@ai-ecoverse/slicc-kernel 1.176.2 → 1.178.0
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 +48 -9
- package/dist/index.js +8 -1
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +519 -121
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.cjs +1 -1
- package/dist/node-process-worker.cjs.map +2 -2
- package/dist/node-process-worker.js +1 -1
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +515 -116
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +1 -1
- package/dist/process-worker.js.map +2 -2
- package/dist/types/index.d.ts +2 -1
- package/dist/types/kernel/sealed-oauth.d.ts +74 -0
- package/dist/types/kernel/sealed.d.ts +1 -0
- package/dist/types/launcher.d.ts +5 -0
- package/dist/types/node.d.ts +7 -1
- package/dist/types/process/js/js-context.d.ts +2 -0
- package/dist/types/sudo-timestamp.d.ts +28 -0
- package/dist/types/sudo.d.ts +20 -1
- package/dist/types/sudoers.d.ts +25 -2
- package/package.json +3 -3
package/dist/kernel-worker.js
CHANGED
|
@@ -1611,10 +1611,10 @@ var RemoteWatch = class {
|
|
|
1611
1611
|
|
|
1612
1612
|
// src/fs/view.ts
|
|
1613
1613
|
function view(target, overrides) {
|
|
1614
|
-
const
|
|
1614
|
+
const keys2 = new Set(Object.keys(overrides));
|
|
1615
1615
|
return new Proxy(target, {
|
|
1616
1616
|
get(t, prop, receiver) {
|
|
1617
|
-
if (
|
|
1617
|
+
if (keys2.has(prop)) return overrides[prop];
|
|
1618
1618
|
return Reflect.get(t, prop, receiver);
|
|
1619
1619
|
},
|
|
1620
1620
|
set(t, prop, value2) {
|
|
@@ -2796,8 +2796,8 @@ var OpfsFs = class {
|
|
|
2796
2796
|
const [entry] = await this.meta.get([target]);
|
|
2797
2797
|
if (entry?.fifo || entry?.socket) throw fsError("ENXIO", path);
|
|
2798
2798
|
const kept = fresh3 ? cached : this.files.get(target);
|
|
2799
|
-
const
|
|
2800
|
-
return [
|
|
2799
|
+
const usable3 = kept && performance.now() - (this.found.get(kept) ?? -CHECK_MS) < CHECK_MS;
|
|
2800
|
+
return [usable3 ? kept : void 0, target, true];
|
|
2801
2801
|
}
|
|
2802
2802
|
async writeAt(handle, target, path, offset, bytes) {
|
|
2803
2803
|
await this.counting(target);
|
|
@@ -3599,8 +3599,8 @@ var OpfsFs = class {
|
|
|
3599
3599
|
renamingDirs = /* @__PURE__ */ new Set();
|
|
3600
3600
|
intents = 0;
|
|
3601
3601
|
async intend(rename, at = `${RENAMES2}/${crypto.randomUUID()}`) {
|
|
3602
|
-
const
|
|
3603
|
-
await this.meta.update(at, () => ({ path: at, rename:
|
|
3602
|
+
const record3 = { ...rename, at: Date.now() * 1e3 + this.intents++ % 1e3 };
|
|
3603
|
+
await this.meta.update(at, () => ({ path: at, rename: record3 }));
|
|
3604
3604
|
return at;
|
|
3605
3605
|
}
|
|
3606
3606
|
async rollBack(from, to, intents) {
|
|
@@ -4145,9 +4145,9 @@ async function same(a, b) {
|
|
|
4145
4145
|
return false;
|
|
4146
4146
|
}
|
|
4147
4147
|
}
|
|
4148
|
-
async function matches(
|
|
4149
|
-
if (
|
|
4150
|
-
return "path" in at &&
|
|
4148
|
+
async function matches(record3, root, at) {
|
|
4149
|
+
if (record3.handle) return same(record3.handle, root);
|
|
4150
|
+
return "path" in at && record3.path !== void 0 && samePath(record3.path, at.path);
|
|
4151
4151
|
}
|
|
4152
4152
|
var nameOf = (id2) => `${META_DB}:${id2}`;
|
|
4153
4153
|
function done(tx) {
|
|
@@ -4190,7 +4190,7 @@ async function rootId(factory, root, locks = globalThis.navigator?.locks, top) {
|
|
|
4190
4190
|
const all = await request(
|
|
4191
4191
|
db.transaction(ROOTS_STORE, "readonly").objectStore(ROOTS_STORE).getAll()
|
|
4192
4192
|
);
|
|
4193
|
-
for (const
|
|
4193
|
+
for (const record3 of all) if (await matches(record3, root, at)) return record3.id;
|
|
4194
4194
|
const id2 = crypto.randomUUID();
|
|
4195
4195
|
if (await used(root)) await seed(factory, nameOf(id2));
|
|
4196
4196
|
const tx = db.transaction(ROOTS_STORE, "readwrite");
|
|
@@ -5673,9 +5673,9 @@ function changeCred(cur, change) {
|
|
|
5673
5673
|
}
|
|
5674
5674
|
const privileged = cur.euid === 0;
|
|
5675
5675
|
const next3 = copyCred(cur);
|
|
5676
|
-
for (const
|
|
5677
|
-
const own =
|
|
5678
|
-
for (const key of
|
|
5676
|
+
for (const keys2 of [UIDS, GIDS]) {
|
|
5677
|
+
const own = keys2.map((key) => cur[key]);
|
|
5678
|
+
for (const key of keys2) {
|
|
5679
5679
|
const value2 = id(change[key]);
|
|
5680
5680
|
if (value2 === void 0) continue;
|
|
5681
5681
|
if (!privileged && !own.includes(value2)) throw fsError("EPERM", `${key} ${value2}`);
|
|
@@ -8682,12 +8682,12 @@ function next(p) {
|
|
|
8682
8682
|
var ANY = [[">=", [0, 0, 0, []]]];
|
|
8683
8683
|
var NOTHING = [["<", [0, 0, 0, ["0"]]]];
|
|
8684
8684
|
function plain(op, p) {
|
|
8685
|
-
const
|
|
8685
|
+
const exact2 = !p.parts.includes(void 0);
|
|
8686
8686
|
const low = floor(p);
|
|
8687
8687
|
const high = next(p);
|
|
8688
8688
|
if (op === ">=") return [[">=", low]];
|
|
8689
|
-
if (op === "<") return [["<",
|
|
8690
|
-
if (
|
|
8689
|
+
if (op === "<") return [["<", exact2 ? low : [low[0], low[1], low[2], ["0"]]]];
|
|
8690
|
+
if (exact2) return [[op === "" ? "=" : op, low]];
|
|
8691
8691
|
if (op === ">") return high ? [[">=", high]] : NOTHING;
|
|
8692
8692
|
if (op === "<=") return high ? [["<", high]] : ANY;
|
|
8693
8693
|
return high ? [
|
|
@@ -8774,7 +8774,7 @@ function satisfies(version, range2) {
|
|
|
8774
8774
|
}
|
|
8775
8775
|
|
|
8776
8776
|
// src/version.ts
|
|
8777
|
-
var KERNEL_VERSION = true ? "1.
|
|
8777
|
+
var KERNEL_VERSION = true ? "1.178.0" : "0.0.0-dev";
|
|
8778
8778
|
var OS_RELEASE = "6.0.0-slicc";
|
|
8779
8779
|
function procVersion() {
|
|
8780
8780
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -9364,7 +9364,7 @@ function intlZone(name2) {
|
|
|
9364
9364
|
minute: "numeric",
|
|
9365
9365
|
second: "numeric"
|
|
9366
9366
|
});
|
|
9367
|
-
const
|
|
9367
|
+
const exact2 = (at) => {
|
|
9368
9368
|
const parts2 = {};
|
|
9369
9369
|
for (const part of format.formatToParts(at)) parts2[part.type] = Number(part.value);
|
|
9370
9370
|
const wall = Date.UTC(
|
|
@@ -9384,12 +9384,12 @@ function intlZone(name2) {
|
|
|
9384
9384
|
const bucket = Math.floor(at / BUCKET_MS);
|
|
9385
9385
|
let known = buckets.get(bucket);
|
|
9386
9386
|
if (known === void 0) {
|
|
9387
|
-
const start2 =
|
|
9388
|
-
known = start2 ===
|
|
9387
|
+
const start2 = exact2(bucket * BUCKET_MS);
|
|
9388
|
+
known = start2 === exact2(bucket * BUCKET_MS + BUCKET_MS - MINUTE_MS) ? start2 : null;
|
|
9389
9389
|
if (buckets.size >= CACHE_MAX) buckets.clear();
|
|
9390
9390
|
buckets.set(bucket, known);
|
|
9391
9391
|
}
|
|
9392
|
-
return known ??
|
|
9392
|
+
return known ?? exact2(at);
|
|
9393
9393
|
}
|
|
9394
9394
|
};
|
|
9395
9395
|
}
|
|
@@ -10619,11 +10619,11 @@ var CronService = class {
|
|
|
10619
10619
|
const now = this.clock.now();
|
|
10620
10620
|
const system = this.host.timeZone;
|
|
10621
10621
|
const due2 = [];
|
|
10622
|
-
const
|
|
10622
|
+
const keys2 = /* @__PURE__ */ new Set();
|
|
10623
10623
|
let next3 = now + WAKE_MS;
|
|
10624
10624
|
for (const entry of loaded) {
|
|
10625
10625
|
const key = keyOf2(entry);
|
|
10626
|
-
|
|
10626
|
+
keys2.add(key);
|
|
10627
10627
|
const { when } = entry.job;
|
|
10628
10628
|
if (when.reboot) {
|
|
10629
10629
|
if (this.rebooted || stamp.reboot[key] === this.host.bootId) continue;
|
|
@@ -10649,7 +10649,7 @@ var CronService = class {
|
|
|
10649
10649
|
}
|
|
10650
10650
|
this.rebooted = true;
|
|
10651
10651
|
for (const map of [stamp.jobs, stamp.reboot, stamp.when])
|
|
10652
|
-
for (const key of Object.keys(map)) if (!
|
|
10652
|
+
for (const key of Object.keys(map)) if (!keys2.has(key)) delete map[key];
|
|
10653
10653
|
await this.writeStamp(stamp);
|
|
10654
10654
|
for (const each of due2) void this.spawn(each);
|
|
10655
10655
|
return next3;
|
|
@@ -11755,16 +11755,16 @@ function missing4(err) {
|
|
|
11755
11755
|
return err?.code === "ENOENT";
|
|
11756
11756
|
}
|
|
11757
11757
|
function synthetic(directory, path) {
|
|
11758
|
-
const
|
|
11758
|
+
const epoch2 = /* @__PURE__ */ new Date(0);
|
|
11759
11759
|
return {
|
|
11760
11760
|
isFile: !directory,
|
|
11761
11761
|
isDirectory: directory,
|
|
11762
11762
|
isSymbolicLink: false,
|
|
11763
11763
|
size: 0,
|
|
11764
11764
|
mode: (directory ? S_IFDIR2 : S_IFREG2) | 493,
|
|
11765
|
-
mtime:
|
|
11766
|
-
atime:
|
|
11767
|
-
ctime:
|
|
11765
|
+
mtime: epoch2,
|
|
11766
|
+
atime: epoch2,
|
|
11767
|
+
ctime: epoch2,
|
|
11768
11768
|
ino: inodeOf(path)
|
|
11769
11769
|
};
|
|
11770
11770
|
}
|
|
@@ -11983,16 +11983,16 @@ function missing5(err) {
|
|
|
11983
11983
|
return err?.code === "ENOENT";
|
|
11984
11984
|
}
|
|
11985
11985
|
function statOf(path, bytes) {
|
|
11986
|
-
const
|
|
11986
|
+
const epoch2 = /* @__PURE__ */ new Date(0);
|
|
11987
11987
|
return {
|
|
11988
11988
|
isFile: true,
|
|
11989
11989
|
isDirectory: false,
|
|
11990
11990
|
isSymbolicLink: false,
|
|
11991
11991
|
size: bytes.length,
|
|
11992
11992
|
mode: S_IFREG3 | 420,
|
|
11993
|
-
mtime:
|
|
11994
|
-
atime:
|
|
11995
|
-
ctime:
|
|
11993
|
+
mtime: epoch2,
|
|
11994
|
+
atime: epoch2,
|
|
11995
|
+
ctime: epoch2,
|
|
11996
11996
|
ino: inodeOf(path),
|
|
11997
11997
|
nlink: 1,
|
|
11998
11998
|
uid: 0,
|
|
@@ -14048,11 +14048,11 @@ var HeldPaths = class extends Map {
|
|
|
14048
14048
|
handlesAt(path) {
|
|
14049
14049
|
return [...this.handles].filter(([, key]) => key === path).map(([id2]) => id2);
|
|
14050
14050
|
}
|
|
14051
|
-
doubt(over,
|
|
14051
|
+
doubt(over, exact2 = false) {
|
|
14052
14052
|
if (this.bound.size === 0) return;
|
|
14053
14053
|
for (const [handle, key] of this.handles) {
|
|
14054
14054
|
if (!this.bound.has(handle) || this.keptHandles.has(handle)) continue;
|
|
14055
|
-
if (
|
|
14055
|
+
if (exact2 ? key === over : within7(key, over)) this.doubted.set(handle, ++this.doubts);
|
|
14056
14056
|
}
|
|
14057
14057
|
}
|
|
14058
14058
|
boundIno(handle) {
|
|
@@ -17907,8 +17907,8 @@ function refused2(engine, session, err, opening2) {
|
|
|
17907
17907
|
const code = verifyCode(flags2);
|
|
17908
17908
|
if (code && flags2 !== -1)
|
|
17909
17909
|
return new KernelError("EPROTO", `${code}: ${engine.verifyInfo(flags2).trim()}`);
|
|
17910
|
-
const
|
|
17911
|
-
if (
|
|
17910
|
+
const record3 = recordRefusal(opening2);
|
|
17911
|
+
if (record3) return record3;
|
|
17912
17912
|
const text = err instanceof TlsError ? err.message : String(err);
|
|
17913
17913
|
return new KernelError("EPROTO", `ERR_SSL_HANDSHAKE_FAILURE: ${text}${alertOf(session)}`);
|
|
17914
17914
|
}
|
|
@@ -18801,8 +18801,8 @@ var SCRUB_IDLE_MS = 50;
|
|
|
18801
18801
|
var SCRUB_NONBLOCK_MAX = 64 * 1024;
|
|
18802
18802
|
var encoder5 = new TextEncoder();
|
|
18803
18803
|
function scrubberOf(secrets, sameLength = false) {
|
|
18804
|
-
const
|
|
18805
|
-
return
|
|
18804
|
+
const usable3 = secrets.filter((s) => encoder5.encode(s.value).length >= MIN_VALUE);
|
|
18805
|
+
return usable3.length > 0 ? compile(usable3, { idleMs: SCRUB_IDLE_MS, sameLength }) : void 0;
|
|
18806
18806
|
}
|
|
18807
18807
|
async function writeAll2(file, bytes) {
|
|
18808
18808
|
const write = file.write;
|
|
@@ -19023,6 +19023,120 @@ var SessionScrubs = class {
|
|
|
19023
19023
|
}
|
|
19024
19024
|
};
|
|
19025
19025
|
|
|
19026
|
+
// src/kernel/sealed-oauth.ts
|
|
19027
|
+
var SEALED_OAUTH_TIMEOUT_MS = 45e3;
|
|
19028
|
+
var SEALED_OAUTH_OPS = /* @__PURE__ */ new Set([
|
|
19029
|
+
"oauthGrant",
|
|
19030
|
+
"oauthStatus",
|
|
19031
|
+
"oauthRefresh",
|
|
19032
|
+
"oauthRemove"
|
|
19033
|
+
]);
|
|
19034
|
+
var MESSAGES = {
|
|
19035
|
+
EINVAL: "Invalid OAuth request",
|
|
19036
|
+
ENOENT: "OAuth credential not found",
|
|
19037
|
+
EEXIST: "OAuth credential identity conflicts",
|
|
19038
|
+
EACCES: "OAuth operation refused",
|
|
19039
|
+
EAGAIN: "OAuth credential changed; try again",
|
|
19040
|
+
ENOTSUP: "OAuth operation is not supported",
|
|
19041
|
+
ENOTCONN: "Vault is unavailable",
|
|
19042
|
+
ETIMEDOUT: "Vault request timed out",
|
|
19043
|
+
EIO: "Invalid vault reply"
|
|
19044
|
+
};
|
|
19045
|
+
var CAUSES = {
|
|
19046
|
+
"vault-locked": "Vault is locked",
|
|
19047
|
+
"oauth-reauth-required": "OAuth sign-in is required",
|
|
19048
|
+
"oauth-refresh-failed": "OAuth refresh failed"
|
|
19049
|
+
};
|
|
19050
|
+
var COMMON = ["v", "op", "caller"];
|
|
19051
|
+
var IDENTITY = [...COMMON, "name", "provider", "account"];
|
|
19052
|
+
var GRANT = [...IDENTITY, "accessToken", "refreshToken", "tokenType", "expiresAt", "oauth"];
|
|
19053
|
+
var CONFIG = ["tokenUrl", "clientId", "authMethod", "scope", "resource"];
|
|
19054
|
+
var UNSUPPORTED = ["auth", "clientSecret", "fields"];
|
|
19055
|
+
var STATES2 = /* @__PURE__ */ new Set([
|
|
19056
|
+
"ready",
|
|
19057
|
+
"refreshing",
|
|
19058
|
+
"expired",
|
|
19059
|
+
"reauth-required",
|
|
19060
|
+
"removed",
|
|
19061
|
+
"locked"
|
|
19062
|
+
]);
|
|
19063
|
+
var NAME3 = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
|
|
19064
|
+
var PROVIDER = /^[a-z0-9][a-z0-9-]{0,127}$/;
|
|
19065
|
+
var PLACEHOLDER2 = /^fnox_proxy_[0-9a-f]{24}$/;
|
|
19066
|
+
var CONTROL = /[\u0000-\u001f\u007f-\u009f]/;
|
|
19067
|
+
function record(value2) {
|
|
19068
|
+
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
19069
|
+
}
|
|
19070
|
+
function keys(value2, allowed) {
|
|
19071
|
+
return Object.keys(value2).every((key) => allowed.includes(key));
|
|
19072
|
+
}
|
|
19073
|
+
function exact(value2, fields) {
|
|
19074
|
+
return Object.keys(value2).length === fields.length && keys(value2, fields);
|
|
19075
|
+
}
|
|
19076
|
+
function nonempty(value2) {
|
|
19077
|
+
return typeof value2 === "string" && value2.length > 0;
|
|
19078
|
+
}
|
|
19079
|
+
function epoch(value2) {
|
|
19080
|
+
return typeof value2 === "number" && Number.isSafeInteger(value2) && value2 >= 0;
|
|
19081
|
+
}
|
|
19082
|
+
function sealedOAuthOperation(op) {
|
|
19083
|
+
return op === "capabilities" || SEALED_OAUTH_OPS.has(op);
|
|
19084
|
+
}
|
|
19085
|
+
function validateSealedOAuth(req) {
|
|
19086
|
+
const config = req.oauth;
|
|
19087
|
+
if (UNSUPPORTED.some((key) => Object.hasOwn(req, key)) || record(config) && UNSUPPORTED.some((key) => Object.hasOwn(config, key)))
|
|
19088
|
+
throw new KernelError("ENOTSUP");
|
|
19089
|
+
const allowed = req.op === "capabilities" ? COMMON : req.op === "oauthGrant" ? GRANT : IDENTITY;
|
|
19090
|
+
if (!keys(req, allowed)) throw new KernelError("EINVAL");
|
|
19091
|
+
if (req.op === "capabilities") return;
|
|
19092
|
+
if (typeof req.name !== "string" || !NAME3.test(req.name) || typeof req.provider !== "string" || !PROVIDER.test(req.provider) || !nonempty(req.account) || req.account.length > 256 || CONTROL.test(req.account))
|
|
19093
|
+
throw new KernelError("EINVAL");
|
|
19094
|
+
if (req.op !== "oauthGrant") return;
|
|
19095
|
+
if (!nonempty(req.accessToken) || req.refreshToken !== void 0 && !nonempty(req.refreshToken) || req.tokenType !== void 0 && typeof req.tokenType !== "string" || req.expiresAt !== void 0 && !epoch(req.expiresAt) || !record(config) || !keys(config, CONFIG) || !nonempty(config.tokenUrl) || !nonempty(config.clientId) || typeof config.authMethod !== "string" || config.scope !== void 0 && typeof config.scope !== "string" || config.resource !== void 0 && !nonempty(config.resource))
|
|
19096
|
+
throw new KernelError("EINVAL");
|
|
19097
|
+
if (config.authMethod !== "none" || typeof req.tokenType === "string" && req.tokenType.toLowerCase() !== "bearer")
|
|
19098
|
+
throw new KernelError("ENOTSUP");
|
|
19099
|
+
}
|
|
19100
|
+
function sealedOAuthError(code = "EIO", cause) {
|
|
19101
|
+
return {
|
|
19102
|
+
v: 1,
|
|
19103
|
+
error: { code, ...cause ? { cause } : {}, message: cause ? CAUSES[cause] : MESSAGES[code] }
|
|
19104
|
+
};
|
|
19105
|
+
}
|
|
19106
|
+
function sealedOAuthReply(op, value2) {
|
|
19107
|
+
try {
|
|
19108
|
+
return checkedReply(op, value2) ?? sealedOAuthError();
|
|
19109
|
+
} catch {
|
|
19110
|
+
return sealedOAuthError();
|
|
19111
|
+
}
|
|
19112
|
+
}
|
|
19113
|
+
function checkedError(value2) {
|
|
19114
|
+
const error = value2.error;
|
|
19115
|
+
if (!exact(value2, ["v", "error"]) || !record(error) || !Object.hasOwn(error, "code") || !Object.hasOwn(error, "message"))
|
|
19116
|
+
return void 0;
|
|
19117
|
+
const { code, cause, message: message3 } = error;
|
|
19118
|
+
if (!keys(error, ["code", "cause", "message"]) || typeof code !== "string" || !Object.hasOwn(MESSAGES, code) || typeof message3 !== "string" || cause !== void 0 && (typeof cause !== "string" || !Object.hasOwn(CAUSES, cause)))
|
|
19119
|
+
return void 0;
|
|
19120
|
+
return sealedOAuthError(code, cause);
|
|
19121
|
+
}
|
|
19122
|
+
function checkedReply(op, value2) {
|
|
19123
|
+
if (!record(value2) || !Object.hasOwn(value2, "v") || value2.v !== 1) return void 0;
|
|
19124
|
+
if (Object.hasOwn(value2, "error")) return checkedError(value2);
|
|
19125
|
+
if (op === "capabilities") {
|
|
19126
|
+
const caps = value2.capabilities;
|
|
19127
|
+
if (!exact(value2, ["v", "capabilities"]) || !record(caps) || !keys(caps, ["kernelOAuth"]))
|
|
19128
|
+
return void 0;
|
|
19129
|
+
if (Object.hasOwn(caps, "kernelOAuth") && caps.kernelOAuth !== 1) return void 0;
|
|
19130
|
+
return { v: 1, capabilities: Object.hasOwn(caps, "kernelOAuth") ? { kernelOAuth: 1 } : {} };
|
|
19131
|
+
}
|
|
19132
|
+
if (op === "oauthRemove")
|
|
19133
|
+
return exact(value2, ["v", "removed"]) && value2.removed === true ? { v: 1, removed: true } : void 0;
|
|
19134
|
+
const { placeholder, state, expiresAt, refreshable } = value2;
|
|
19135
|
+
if (!exact(value2, ["v", "placeholder", "state", "expiresAt", "refreshable"]) || typeof placeholder !== "string" || !PLACEHOLDER2.test(placeholder) || typeof state !== "string" || !STATES2.has(state) || expiresAt !== null && !epoch(expiresAt) || typeof refreshable !== "boolean")
|
|
19136
|
+
return void 0;
|
|
19137
|
+
return { v: 1, placeholder, state, expiresAt, refreshable };
|
|
19138
|
+
}
|
|
19139
|
+
|
|
19026
19140
|
// src/kernel/sealed-session.ts
|
|
19027
19141
|
var SESSION_WINDOW_MS = 6e4;
|
|
19028
19142
|
var SESSION_PLACEHOLDERS = 16;
|
|
@@ -19133,8 +19247,8 @@ function sessionValues(reply, asked) {
|
|
|
19133
19247
|
const { error, values } = reply;
|
|
19134
19248
|
if (error !== void 0 || typeof values !== "object" || values === null) return void 0;
|
|
19135
19249
|
const out = {};
|
|
19136
|
-
const
|
|
19137
|
-
const wanted = asked ?? (
|
|
19250
|
+
const keys2 = Object.keys(values);
|
|
19251
|
+
const wanted = asked ?? (keys2.length === 1 && SESSION_PLACEHOLDER.test(keys2[0]) ? keys2 : []);
|
|
19138
19252
|
for (const ph of wanted) {
|
|
19139
19253
|
const value2 = values[ph];
|
|
19140
19254
|
if (typeof value2 === "string" && value2.length > 0 && !UNSAFE.test(value2)) out[ph] = value2;
|
|
@@ -19158,7 +19272,9 @@ var SEALED_OPS = /* @__PURE__ */ new Set([
|
|
|
19158
19272
|
"request",
|
|
19159
19273
|
"status",
|
|
19160
19274
|
"cancel",
|
|
19161
|
-
"materialize"
|
|
19275
|
+
"materialize",
|
|
19276
|
+
"capabilities",
|
|
19277
|
+
...SEALED_OAUTH_OPS
|
|
19162
19278
|
]);
|
|
19163
19279
|
function tagFields(tags) {
|
|
19164
19280
|
const [tag] = tags ?? [];
|
|
@@ -19193,6 +19309,7 @@ function parseSealed(bytes) {
|
|
|
19193
19309
|
throw new KernelError("EINVAL");
|
|
19194
19310
|
if (req.op === "materialize" && !materializable(req)) throw new KernelError("EINVAL");
|
|
19195
19311
|
if (req.op === "ask" && !askable(req)) throw new KernelError("EINVAL");
|
|
19312
|
+
if (sealedOAuthOperation(req.op)) validateSealedOAuth(req);
|
|
19196
19313
|
return req;
|
|
19197
19314
|
} finally {
|
|
19198
19315
|
bytes.fill(0);
|
|
@@ -19271,7 +19388,7 @@ var SealedChannel = class {
|
|
|
19271
19388
|
const ask = fields.op === "ask";
|
|
19272
19389
|
const timer = setTimeout(
|
|
19273
19390
|
() => control.abort(ask ? "expired" : "timeout"),
|
|
19274
|
-
ask ? this.broker.askTimeoutMs : this.broker.timeoutMs
|
|
19391
|
+
ask ? this.broker.askTimeoutMs : SEALED_OAUTH_OPS.has(fields.op) ? this.broker.oauthTimeoutMs : this.broker.timeoutMs
|
|
19275
19392
|
);
|
|
19276
19393
|
const req = { ...fields, v: 1, op: fields.op, caller };
|
|
19277
19394
|
const asked = ask ? this.asking(fields, caller, requester, control.signal) : void 0;
|
|
@@ -19294,24 +19411,26 @@ var SealedChannel = class {
|
|
|
19294
19411
|
if (!facts) return errorReply("EIO", "the kernel has no record of this process");
|
|
19295
19412
|
return this.answer(askRequest(fields, caller, facts), signal);
|
|
19296
19413
|
}
|
|
19297
|
-
stopped(signal) {
|
|
19414
|
+
stopped(signal, oauth = false) {
|
|
19298
19415
|
if (signal.reason === "timeout")
|
|
19299
|
-
return errorReply("ETIMEDOUT", "the vault did not answer within 10 s");
|
|
19416
|
+
return oauth ? sealedOAuthError("ETIMEDOUT") : errorReply("ETIMEDOUT", "the vault did not answer within 10 s");
|
|
19300
19417
|
return signal.reason === "expired" ? { v: 1, state: "expired" } : void 0;
|
|
19301
19418
|
}
|
|
19302
19419
|
async answer(req, signal) {
|
|
19303
19420
|
const hook = this.broker.hook;
|
|
19304
|
-
|
|
19421
|
+
const oauth = sealedOAuthOperation(req.op);
|
|
19422
|
+
if (!hook)
|
|
19423
|
+
return oauth ? sealedOAuthError("ENOTCONN") : errorReply("ENOTCONN", "no vault is connected");
|
|
19305
19424
|
const stopped = new Promise((resolve2) => {
|
|
19306
19425
|
signal.addEventListener("abort", () => resolve2(void 0), { once: true });
|
|
19307
19426
|
});
|
|
19308
19427
|
if (signal.aborted) return void 0;
|
|
19309
19428
|
const asked = Promise.resolve().then(() => hook(req, { signal })).then(
|
|
19310
|
-
(reply2) => isRecord(reply2) ? reply2 : errorReply("EIO", "the vault gave no reply"),
|
|
19311
|
-
(err) => errorReply("EIO", lineOf3(err))
|
|
19429
|
+
(reply2) => oauth ? sealedOAuthReply(req.op, reply2) : isRecord(reply2) ? reply2 : errorReply("EIO", "the vault gave no reply"),
|
|
19430
|
+
(err) => oauth ? sealedOAuthError() : errorReply("EIO", lineOf3(err))
|
|
19312
19431
|
);
|
|
19313
19432
|
const reply = await Promise.race([asked, stopped]);
|
|
19314
|
-
return signal.aborted ? this.stopped(signal) : reply;
|
|
19433
|
+
return signal.aborted ? this.stopped(signal, oauth) : reply;
|
|
19315
19434
|
}
|
|
19316
19435
|
drop() {
|
|
19317
19436
|
this.reply?.fill(0);
|
|
@@ -19374,6 +19493,7 @@ var SealedBroker = class {
|
|
|
19374
19493
|
tickets = new TicketBook();
|
|
19375
19494
|
sessions = new SessionScrubs();
|
|
19376
19495
|
askTimeoutMs = SEALED_ASK_TIMEOUT_MS;
|
|
19496
|
+
oauthTimeoutMs = SEALED_OAUTH_TIMEOUT_MS;
|
|
19377
19497
|
userOf = async () => void 0;
|
|
19378
19498
|
factsOf = async () => void 0;
|
|
19379
19499
|
constructor(hook, users, timeoutMs = SEALED_TIMEOUT_MS, tagOf = () => void 0) {
|
|
@@ -19441,10 +19561,60 @@ var SealedBroker = class {
|
|
|
19441
19561
|
}
|
|
19442
19562
|
};
|
|
19443
19563
|
|
|
19564
|
+
// src/sudo-timestamp.ts
|
|
19565
|
+
var TIMESTAMPS_MAX = 64;
|
|
19566
|
+
var MINUTE_MS2 = 6e4;
|
|
19567
|
+
function timestampKey(owner, type) {
|
|
19568
|
+
if (type === "global") return "global";
|
|
19569
|
+
const { terminal, parent } = owner;
|
|
19570
|
+
if (type === "tty" && terminal) {
|
|
19571
|
+
return JSON.stringify(["tty", terminal.tty, terminal.sid, terminal.started]);
|
|
19572
|
+
}
|
|
19573
|
+
return parent ? JSON.stringify(["ppid", parent.pid, parent.started]) : void 0;
|
|
19574
|
+
}
|
|
19575
|
+
var Timestamps = class {
|
|
19576
|
+
users = /* @__PURE__ */ new Map();
|
|
19577
|
+
now;
|
|
19578
|
+
constructor(now) {
|
|
19579
|
+
this.now = now;
|
|
19580
|
+
}
|
|
19581
|
+
current(owner, policy) {
|
|
19582
|
+
if (policy.minutes === 0) return false;
|
|
19583
|
+
const key = timestampKey(owner, policy.type);
|
|
19584
|
+
const at = key === void 0 ? void 0 : this.users.get(owner.uid)?.get(key);
|
|
19585
|
+
if (at === void 0) return false;
|
|
19586
|
+
const now = this.now();
|
|
19587
|
+
return at <= now && (policy.minutes < 0 || now - at < policy.minutes * MINUTE_MS2);
|
|
19588
|
+
}
|
|
19589
|
+
stamp(owner, policy) {
|
|
19590
|
+
const key = policy.minutes === 0 ? void 0 : timestampKey(owner, policy.type);
|
|
19591
|
+
if (key === void 0) return false;
|
|
19592
|
+
const now = this.now();
|
|
19593
|
+
const records = this.users.get(owner.uid) ?? /* @__PURE__ */ new Map();
|
|
19594
|
+
this.users.set(owner.uid, records);
|
|
19595
|
+
records.delete(key);
|
|
19596
|
+
records.set(key, now);
|
|
19597
|
+
for (const [old, at] of records) {
|
|
19598
|
+
const stale = policy.minutes > 0 && (at > now || now - at >= policy.minutes * MINUTE_MS2);
|
|
19599
|
+
if (stale || records.size > TIMESTAMPS_MAX) records.delete(old);
|
|
19600
|
+
}
|
|
19601
|
+
return true;
|
|
19602
|
+
}
|
|
19603
|
+
invalidate(owner, type) {
|
|
19604
|
+
const key = timestampKey(owner, type);
|
|
19605
|
+
const records = this.users.get(owner.uid);
|
|
19606
|
+
if (key !== void 0) records?.delete(key);
|
|
19607
|
+
if (records?.size === 0) this.users.delete(owner.uid);
|
|
19608
|
+
}
|
|
19609
|
+
remove(uid) {
|
|
19610
|
+
this.users.delete(uid);
|
|
19611
|
+
}
|
|
19612
|
+
};
|
|
19613
|
+
|
|
19444
19614
|
// src/sudoers.ts
|
|
19445
19615
|
var SUDOERS = "/etc/sudoers";
|
|
19446
19616
|
var SUDOERS_D = "/etc/sudoers.d";
|
|
19447
|
-
var
|
|
19617
|
+
var NAME4 = /^[a-z_][a-z0-9_.-]*$/;
|
|
19448
19618
|
var WORD = String.raw`(?:'[^']*'|"(?:[^"\\]|\\[\s\S])*"|\\[\s\S]|[^\s'"\\])+`;
|
|
19449
19619
|
var KERNEL_FORM = new RegExp(
|
|
19450
19620
|
String.raw`^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()]*)\)\s+)?(?:Cwd\s+(${WORD})\s+)?(?:Stdin\s+(\S+)\s+)?Cmnd(?:\s+(.*))?$`
|
|
@@ -19454,14 +19624,16 @@ var RUNAS = /^\(([^()]*)\)\s*/;
|
|
|
19454
19624
|
var TAG = /^([A-Z_]+)\s*:\s*/;
|
|
19455
19625
|
var OPTION = /^([A-Z_]+)=/;
|
|
19456
19626
|
var PLAIN2 = /^[A-Za-z0-9_@%+=:,./-]+$/;
|
|
19457
|
-
var
|
|
19627
|
+
var CONTROL2 = /[\p{Cc}\u2028\u2029]/u;
|
|
19458
19628
|
var CONTROLS = /[\p{Cc}\u2028\u2029]/gu;
|
|
19459
19629
|
var SHOWN_MAX = 60;
|
|
19460
19630
|
var KERNEL_SYNTAX = "[NOPASSWD] <user|%group|ALL> [(<target>)] [Cwd <dir>] [Stdin none] Cmnd <command\u2026|*>";
|
|
19461
19631
|
var SUDOERS_SYNTAX = "<user|%group|ALL> ALL=[(<target>[,<target>\u2026])] [NOPASSWD:] <command\u2026|ALL>";
|
|
19462
19632
|
var EXPECTED = `expected '${KERNEL_SYNTAX}' or '${SUDOERS_SYNTAX}'`;
|
|
19633
|
+
var TIMESTAMP_TIMEOUT = 5;
|
|
19634
|
+
var TIMESTAMP_TYPE = "tty";
|
|
19463
19635
|
function hasControl(text) {
|
|
19464
|
-
return
|
|
19636
|
+
return CONTROL2.test(text);
|
|
19465
19637
|
}
|
|
19466
19638
|
function withoutControl(text) {
|
|
19467
19639
|
return text.replace(CONTROLS, (ch) => {
|
|
@@ -19599,7 +19771,7 @@ function commandText(cmnd) {
|
|
|
19599
19771
|
}
|
|
19600
19772
|
function validWho(who) {
|
|
19601
19773
|
if (who === "ALL") return true;
|
|
19602
|
-
return
|
|
19774
|
+
return NAME4.test(who.startsWith("%") ? who.slice(1) : who);
|
|
19603
19775
|
}
|
|
19604
19776
|
function whoError(who) {
|
|
19605
19777
|
if (validWho(who)) return void 0;
|
|
@@ -19632,7 +19804,7 @@ function kernelRules(m) {
|
|
|
19632
19804
|
const [, nopasswd, who = "", target = "root", dir, stdin, rest2 = ""] = m;
|
|
19633
19805
|
const bad = whoError(who);
|
|
19634
19806
|
if (bad !== void 0) return bad;
|
|
19635
|
-
if (target !== "ALL" && !
|
|
19807
|
+
if (target !== "ALL" && !NAME4.test(target)) return targetError(target);
|
|
19636
19808
|
const cwd = dir === void 0 ? void 0 : cwdOf(dir);
|
|
19637
19809
|
if (cwd === "") return `Cwd expects one absolute directory, not ${shown(dir)}`;
|
|
19638
19810
|
if (stdin !== void 0 && stdin !== "none") return `Stdin takes none, not ${shown(stdin)}`;
|
|
@@ -19656,11 +19828,11 @@ function runasOf(list) {
|
|
|
19656
19828
|
return colon < 0 ? "() names no user to run as: name one, or leave the (\u2026) out for root" : `(${list}) runs as the caller with another group, which the kernel's sudo can't do`;
|
|
19657
19829
|
}
|
|
19658
19830
|
for (const name2 of names2) {
|
|
19659
|
-
if (name2 === "ALL" ||
|
|
19831
|
+
if (name2 === "ALL" || NAME4.test(name2)) continue;
|
|
19660
19832
|
return `expected user names or ALL to run as, not ${shown(name2)} (no %group, #uid or !user)`;
|
|
19661
19833
|
}
|
|
19662
19834
|
for (const group of groups) {
|
|
19663
|
-
if (group === "" || group === "ALL" ||
|
|
19835
|
+
if (group === "" || group === "ALL" || NAME4.test(group)) continue;
|
|
19664
19836
|
return `expected group names or ALL after ':' in (\u2026), not ${shown(group)}`;
|
|
19665
19837
|
}
|
|
19666
19838
|
return names2.includes("ALL") ? ["ALL"] : [...new Set(names2)];
|
|
@@ -19742,9 +19914,6 @@ function directiveError(text) {
|
|
|
19742
19914
|
return /^#-?\d+\s+\S+\s*=/.test(text) ? "a #uid isn't supported: name the user, %group or ALL" : null;
|
|
19743
19915
|
}
|
|
19744
19916
|
const first = /^\S+/.exec(text)?.[0] ?? "";
|
|
19745
|
-
if (/^Defaults([:@>!]|$)/.test(first)) {
|
|
19746
|
-
return "Defaults aren't supported: the kernel's sudo has no settings, so this changes nothing";
|
|
19747
|
-
}
|
|
19748
19917
|
if (/^[A-Z][A-Za-z]*_Alias$/.test(first)) {
|
|
19749
19918
|
return "aliases aren't supported: name the users, targets and commands in each line";
|
|
19750
19919
|
}
|
|
@@ -19752,7 +19921,7 @@ function directiveError(text) {
|
|
|
19752
19921
|
}
|
|
19753
19922
|
function recordedBare(line, file, rule) {
|
|
19754
19923
|
if (rule.cmnd === "*" || rule.cmnd.args !== void 0 || !rule.nopasswd) return false;
|
|
19755
|
-
if (file !== `${SUDOERS_D}/${rule.who}` || !
|
|
19924
|
+
if (file !== `${SUDOERS_D}/${rule.who}` || !NAME4.test(rule.who) || !NAME4.test(rule.target)) {
|
|
19756
19925
|
return false;
|
|
19757
19926
|
}
|
|
19758
19927
|
const head = `NOPASSWD ${rule.who} (${rule.target}) `;
|
|
@@ -19770,9 +19939,90 @@ function kernelLine(m, line, file) {
|
|
|
19770
19939
|
)
|
|
19771
19940
|
};
|
|
19772
19941
|
}
|
|
19942
|
+
var DEFAULTS = /^Defaults(?=[:@>!\s]|$)/;
|
|
19943
|
+
var DEFAULTS_ONLY = `Defaults aren't supported, except timestamp_timeout and timestamp_type for everyone, a user or a %group: this line changes nothing`;
|
|
19944
|
+
var PARAM = /^(!*)([A-Za-z_]+)(?:\s*([-+]?=)\s*(.*))?$/s;
|
|
19945
|
+
var MINUTES = /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)$/;
|
|
19946
|
+
var TYPES = ["tty", "ppid", "global"];
|
|
19947
|
+
function defaultsValue(raw2) {
|
|
19948
|
+
const quoted = /^"(.*)"$/s.exec(raw2);
|
|
19949
|
+
return quoted ? quoted[1] : raw2;
|
|
19950
|
+
}
|
|
19951
|
+
function timeoutParam({ text, negated, op, value: value2 }) {
|
|
19952
|
+
if (negated && op === void 0) return { timestampTimeout: 0 };
|
|
19953
|
+
if (negated || op !== "=" || !MINUTES.test(value2) || !Number.isFinite(Number(value2))) {
|
|
19954
|
+
return `timestamp_timeout takes minutes, such as =5 or =2.5 (0 always asks, below 0 never expires), not ${shown(text)}`;
|
|
19955
|
+
}
|
|
19956
|
+
return { timestampTimeout: Number(value2) };
|
|
19957
|
+
}
|
|
19958
|
+
function typeParam({ text, negated, op, value: value2 }) {
|
|
19959
|
+
if (!negated && op === "=" && TYPES.includes(value2)) {
|
|
19960
|
+
return { timestampType: value2 };
|
|
19961
|
+
}
|
|
19962
|
+
if (value2 === "kernel") return "timestamp_type=kernel isn't supported: use tty, ppid or global";
|
|
19963
|
+
return `timestamp_type takes =tty, =ppid or =global, not ${shown(text)}`;
|
|
19964
|
+
}
|
|
19965
|
+
function defaultsParam(param) {
|
|
19966
|
+
const m = PARAM.exec(param);
|
|
19967
|
+
if (!m) return DEFAULTS_ONLY;
|
|
19968
|
+
const [, bangs = "", name2, op, raw2 = ""] = m;
|
|
19969
|
+
const read = { text: param, negated: bangs !== "", op, value: defaultsValue(raw2.trim()) };
|
|
19970
|
+
const settings = name2 === "timestamp_timeout" ? timeoutParam(read) : name2 === "timestamp_type" ? typeParam(read) : DEFAULTS_ONLY;
|
|
19971
|
+
if (typeof settings === "string") return settings;
|
|
19972
|
+
const value2 = settings.timestampTimeout ?? settings.timestampType;
|
|
19973
|
+
return { settings, text: `${name2}=${value2}` };
|
|
19974
|
+
}
|
|
19975
|
+
function defaultsWho(binding) {
|
|
19976
|
+
if (binding === "") return null;
|
|
19977
|
+
if (!binding.startsWith(":")) return DEFAULTS_ONLY;
|
|
19978
|
+
const who = binding.slice(1).split(",");
|
|
19979
|
+
for (const name2 of who) {
|
|
19980
|
+
if (!validWho(name2))
|
|
19981
|
+
return `expected users, %groups or ALL after Defaults:, not ${shown(name2)}`;
|
|
19982
|
+
}
|
|
19983
|
+
return who;
|
|
19984
|
+
}
|
|
19985
|
+
function defaultsLine(text) {
|
|
19986
|
+
const m = /^Defaults(\S*)(?:\s+(.*))?$/s.exec(text);
|
|
19987
|
+
const who = defaultsWho(m?.[1] ?? "");
|
|
19988
|
+
if (typeof who === "string") return { error: who };
|
|
19989
|
+
const list = (m?.[2] ?? "").trim();
|
|
19990
|
+
if (list === "")
|
|
19991
|
+
return {
|
|
19992
|
+
error: "expected timestamp_timeout=<minutes> or timestamp_type=<type> after Defaults"
|
|
19993
|
+
};
|
|
19994
|
+
const settings = {};
|
|
19995
|
+
const params = [];
|
|
19996
|
+
for (const param of list.split(",")) {
|
|
19997
|
+
const read = defaultsParam(param.trim());
|
|
19998
|
+
if (typeof read === "string") return { error: read };
|
|
19999
|
+
Object.assign(settings, read.settings);
|
|
20000
|
+
params.push(read.text);
|
|
20001
|
+
}
|
|
20002
|
+
return { defaults: { who, settings, params } };
|
|
20003
|
+
}
|
|
20004
|
+
function resolveDefaults(entries, applies) {
|
|
20005
|
+
const everyone = entries.filter((e) => e.who === null);
|
|
20006
|
+
const mine = entries.filter((e) => e.who?.some(applies));
|
|
20007
|
+
const matching = [...everyone, ...mine];
|
|
20008
|
+
const settings = Object.assign({}, ...matching.map((e) => e.settings));
|
|
20009
|
+
return {
|
|
20010
|
+
timestampTimeout: settings.timestampTimeout ?? TIMESTAMP_TIMEOUT,
|
|
20011
|
+
timestampType: settings.timestampType ?? TIMESTAMP_TYPE,
|
|
20012
|
+
matching
|
|
20013
|
+
};
|
|
20014
|
+
}
|
|
20015
|
+
function defaultsText(entries) {
|
|
20016
|
+
const all = resolveDefaults(entries, () => false);
|
|
20017
|
+
return [
|
|
20018
|
+
`Defaults timestamp_timeout=${all.timestampTimeout}, timestamp_type=${all.timestampType}`,
|
|
20019
|
+
...entries.filter((e) => e.who !== null).map((e) => `Defaults:${e.who.join(",")} ${e.params.join(", ")}`)
|
|
20020
|
+
];
|
|
20021
|
+
}
|
|
19773
20022
|
function parseLine(line, file) {
|
|
19774
20023
|
const text = line.trim();
|
|
19775
20024
|
if (text === "") return null;
|
|
20025
|
+
if (DEFAULTS.test(text)) return defaultsLine(text);
|
|
19776
20026
|
const directive = directiveError(text);
|
|
19777
20027
|
if (directive !== void 0) return directive === null ? null : { error: directive };
|
|
19778
20028
|
const kernel = KERNEL_FORM.exec(text);
|
|
@@ -19784,14 +20034,16 @@ function parseLine(line, file) {
|
|
|
19784
20034
|
}
|
|
19785
20035
|
function parsePolicy(text, file) {
|
|
19786
20036
|
const rules = [];
|
|
20037
|
+
const defaults2 = [];
|
|
19787
20038
|
const errors = [];
|
|
19788
20039
|
for (const [n, line] of text.split("\n").entries()) {
|
|
19789
20040
|
const parsed2 = parseLine(line, file);
|
|
19790
20041
|
if (parsed2 === null) continue;
|
|
19791
20042
|
if ("error" in parsed2) errors.push({ line: n + 1, message: parsed2.error });
|
|
20043
|
+
else if ("defaults" in parsed2) defaults2.push(parsed2.defaults);
|
|
19792
20044
|
else rules.push(...parsed2.rules);
|
|
19793
20045
|
}
|
|
19794
|
-
return { rules, errors };
|
|
20046
|
+
return { rules, defaults: defaults2, errors };
|
|
19795
20047
|
}
|
|
19796
20048
|
|
|
19797
20049
|
// src/sudo.ts
|
|
@@ -19799,7 +20051,14 @@ var SECURE_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
|
19799
20051
|
var DEFAULT_SUDOERS = "%sudo Cmnd *\n";
|
|
19800
20052
|
var SUDO_TIMEOUT_MS = 3e5;
|
|
19801
20053
|
var TERM = /^[A-Za-z0-9._+-]+$/;
|
|
19802
|
-
var USAGE =
|
|
20054
|
+
var USAGE = [
|
|
20055
|
+
"usage: sudo -K | -k",
|
|
20056
|
+
" sudo -v [-kn] [-u user]",
|
|
20057
|
+
" sudo -l [-kn]",
|
|
20058
|
+
" sudo [-kn] [-u user] [--] command [args...]"
|
|
20059
|
+
].join("\n");
|
|
20060
|
+
var DONE = { done: true };
|
|
20061
|
+
var NOT_STAMPED = "sudo: approved, but the approver didn't say a person decided, so no timestamp was recorded";
|
|
19803
20062
|
var DENIED2 = "sudo: approval denied";
|
|
19804
20063
|
var DENIAL_MAX = 500;
|
|
19805
20064
|
var ABORTED = /* @__PURE__ */ Symbol("aborted");
|
|
@@ -19873,6 +20132,17 @@ async function answerOf(approve, req, signal) {
|
|
|
19873
20132
|
return cut === void 0 ? "deny" : await cut;
|
|
19874
20133
|
}
|
|
19875
20134
|
}
|
|
20135
|
+
function approvalOf(answer2) {
|
|
20136
|
+
if (answer2 === "once" || answer2 === "always") return { answer: answer2, person: false };
|
|
20137
|
+
if (typeof answer2 !== "object" || answer2 === null) return void 0;
|
|
20138
|
+
try {
|
|
20139
|
+
const { answer: kind, timestamp } = answer2;
|
|
20140
|
+
if (kind !== "once" && kind !== "always" || timestamp !== true) return void 0;
|
|
20141
|
+
return { answer: kind, person: true };
|
|
20142
|
+
} catch {
|
|
20143
|
+
return void 0;
|
|
20144
|
+
}
|
|
20145
|
+
}
|
|
19876
20146
|
function recordedCommand(argv) {
|
|
19877
20147
|
const [path = "", ...args] = argv;
|
|
19878
20148
|
return commandText({ path, args: args.map((a) => [a]) });
|
|
@@ -19882,6 +20152,9 @@ function flags(parsed2, arg, next3) {
|
|
|
19882
20152
|
const flag = arg[j];
|
|
19883
20153
|
if (flag === "n") parsed2.nonInteractive = true;
|
|
19884
20154
|
else if (flag === "l") parsed2.list = true;
|
|
20155
|
+
else if (flag === "v") parsed2.validate = true;
|
|
20156
|
+
else if (flag === "k") parsed2.reset = true;
|
|
20157
|
+
else if (flag === "K") parsed2.removeAll = true;
|
|
19885
20158
|
else if (flag !== "u") return `sudo: invalid option -- '${flag}'
|
|
19886
20159
|
${USAGE}`;
|
|
19887
20160
|
else {
|
|
@@ -19895,7 +20168,15 @@ ${USAGE}`;
|
|
|
19895
20168
|
return 0;
|
|
19896
20169
|
}
|
|
19897
20170
|
function parseArgs(args) {
|
|
19898
|
-
const parsed2 = {
|
|
20171
|
+
const parsed2 = {
|
|
20172
|
+
list: false,
|
|
20173
|
+
nonInteractive: false,
|
|
20174
|
+
validate: false,
|
|
20175
|
+
reset: false,
|
|
20176
|
+
removeAll: false,
|
|
20177
|
+
user: "root",
|
|
20178
|
+
command: []
|
|
20179
|
+
};
|
|
19899
20180
|
let i = 0;
|
|
19900
20181
|
for (; i < args.length; i++) {
|
|
19901
20182
|
const arg = args[i];
|
|
@@ -19909,8 +20190,17 @@ function parseArgs(args) {
|
|
|
19909
20190
|
i += used2;
|
|
19910
20191
|
}
|
|
19911
20192
|
parsed2.command = args.slice(i);
|
|
19912
|
-
|
|
19913
|
-
|
|
20193
|
+
return usable(parsed2) ? parsed2 : USAGE;
|
|
20194
|
+
}
|
|
20195
|
+
function usable(parsed2) {
|
|
20196
|
+
const command = parsed2.command.length > 0;
|
|
20197
|
+
const modes = [parsed2.list, parsed2.validate, parsed2.removeAll].filter(Boolean).length;
|
|
20198
|
+
if (modes > 1 || parsed2.removeAll && parsed2.reset) return false;
|
|
20199
|
+
if (modes === 1) return !command;
|
|
20200
|
+
return command || parsed2.reset;
|
|
20201
|
+
}
|
|
20202
|
+
function policyOf(settings) {
|
|
20203
|
+
return { type: settings.timestampType, minutes: settings.timestampTimeout };
|
|
19914
20204
|
}
|
|
19915
20205
|
function described(rule) {
|
|
19916
20206
|
const cmnd = rule.cmnd === "*" ? "ALL" : commandText(rule.cmnd);
|
|
@@ -19922,9 +20212,11 @@ var Sudo = class {
|
|
|
19922
20212
|
o;
|
|
19923
20213
|
warned = /* @__PURE__ */ new Set();
|
|
19924
20214
|
pending = /* @__PURE__ */ new Set();
|
|
20215
|
+
timestamps;
|
|
19925
20216
|
next = 0;
|
|
19926
20217
|
constructor(options) {
|
|
19927
20218
|
this.o = options;
|
|
20219
|
+
this.timestamps = new Timestamps(options.now ?? (() => performance.now()));
|
|
19928
20220
|
}
|
|
19929
20221
|
warn(message3) {
|
|
19930
20222
|
if (this.warned.has(message3)) return;
|
|
@@ -20011,8 +20303,8 @@ var Sudo = class {
|
|
|
20011
20303
|
const code = err.code;
|
|
20012
20304
|
return skipped(`it can't be read (${typeof code === "string" ? code : "error"})`);
|
|
20013
20305
|
}
|
|
20014
|
-
const { rules, errors } = parsePolicy(text, path);
|
|
20015
|
-
return { path, rules, problems: errors, ok: true };
|
|
20306
|
+
const { rules, defaults: defaults2, errors } = parsePolicy(text, path);
|
|
20307
|
+
return { path, rules, defaults: defaults2, problems: errors, ok: true };
|
|
20016
20308
|
}
|
|
20017
20309
|
async policy() {
|
|
20018
20310
|
const files = [await this.policyFile(SUDOERS)];
|
|
@@ -20041,7 +20333,10 @@ var Sudo = class {
|
|
|
20041
20333
|
}
|
|
20042
20334
|
}
|
|
20043
20335
|
if (!detailed && skipped > 0) await caller.report?.(skippedNotice(skipped));
|
|
20044
|
-
return
|
|
20336
|
+
return {
|
|
20337
|
+
rules: files.flatMap((f) => f.rules),
|
|
20338
|
+
defaults: files.flatMap((f) => f.defaults ?? [])
|
|
20339
|
+
};
|
|
20045
20340
|
}
|
|
20046
20341
|
async check(cred, call) {
|
|
20047
20342
|
const lines2 = [];
|
|
@@ -20054,17 +20349,26 @@ var Sudo = class {
|
|
|
20054
20349
|
if (read && problems.length === 0) lines2.push([1, `${path}: parsed OK`]);
|
|
20055
20350
|
if (problems.length > 0) ok4 = false;
|
|
20056
20351
|
};
|
|
20352
|
+
const values = (defaults2) => {
|
|
20353
|
+
for (const line of defaultsText(defaults2)) lines2.push([1, line]);
|
|
20354
|
+
};
|
|
20057
20355
|
if (call.files !== void 0) {
|
|
20356
|
+
const defaults2 = [];
|
|
20058
20357
|
for (const { name: name2, text } of call.files) {
|
|
20059
|
-
|
|
20358
|
+
const parsed2 = parsePolicy(text);
|
|
20359
|
+
defaults2.push(...parsed2.defaults);
|
|
20360
|
+
report({ path: name2, rules: [], problems: parsed2.errors, ok: true });
|
|
20060
20361
|
}
|
|
20362
|
+
if (defaults2.length > 0) values(defaults2);
|
|
20061
20363
|
return { lines: lines2, ok: ok4 };
|
|
20062
20364
|
}
|
|
20063
20365
|
if (this.o.mode === "enforce" && cred.euid !== 0) {
|
|
20064
20366
|
const hint = "check the policy as root (sudo visudo -c), or a file with visudo -c -f <file>";
|
|
20065
20367
|
return { lines: [[2, `visudo: ${SUDOERS}: Permission denied: ${hint}`]], ok: false };
|
|
20066
20368
|
}
|
|
20067
|
-
|
|
20369
|
+
const files = await this.policy();
|
|
20370
|
+
for (const file of files) report(file);
|
|
20371
|
+
values(files.flatMap((f) => f.defaults ?? []));
|
|
20068
20372
|
return { lines: lines2, ok: ok4 };
|
|
20069
20373
|
}
|
|
20070
20374
|
async groupIds() {
|
|
@@ -20073,9 +20377,9 @@ var Sudo = class {
|
|
|
20073
20377
|
async applies(caller, cred) {
|
|
20074
20378
|
const gids = await this.groupIds();
|
|
20075
20379
|
const mine = /* @__PURE__ */ new Set([cred.rgid, cred.egid, ...cred.groups]);
|
|
20076
|
-
return (
|
|
20077
|
-
if (
|
|
20078
|
-
const gid =
|
|
20380
|
+
return (who) => {
|
|
20381
|
+
if (who === "ALL" || who === caller.name) return true;
|
|
20382
|
+
const gid = who.startsWith("%") ? gids.get(who.slice(1)) : void 0;
|
|
20079
20383
|
return gid !== void 0 && mine.has(gid);
|
|
20080
20384
|
};
|
|
20081
20385
|
}
|
|
@@ -20120,18 +20424,40 @@ var Sudo = class {
|
|
|
20120
20424
|
const mode = this.o.mode;
|
|
20121
20425
|
if (parsed2.list) return { list: `sudo: users are '${mode}': commands run as the caller
|
|
20122
20426
|
` };
|
|
20123
|
-
return { run: parsed2.command };
|
|
20427
|
+
return parsed2.command.length > 0 ? { run: parsed2.command } : DONE;
|
|
20124
20428
|
}
|
|
20125
20429
|
const { cred } = caller;
|
|
20126
20430
|
const policy = await this.policyRules(caller);
|
|
20127
20431
|
const self = await this.o.users.lookup(cred.ruid);
|
|
20128
20432
|
if (!self) return failed2("EPERM", `sudo: uid ${cred.ruid} is not in the passwd database`);
|
|
20433
|
+
const owner = {
|
|
20434
|
+
uid: cred.ruid,
|
|
20435
|
+
terminal: caller.terminal,
|
|
20436
|
+
parent: caller.parent
|
|
20437
|
+
};
|
|
20438
|
+
const applies = await this.applies(self, cred);
|
|
20439
|
+
const settings = resolveDefaults(policy.defaults, applies);
|
|
20440
|
+
if (parsed2.removeAll) {
|
|
20441
|
+
this.timestamps.remove(owner.uid);
|
|
20442
|
+
return DONE;
|
|
20443
|
+
}
|
|
20444
|
+
if (parsed2.reset && !parsed2.validate && !parsed2.list && parsed2.command.length === 0) {
|
|
20445
|
+
this.timestamps.invalidate(owner, settings.timestampType);
|
|
20446
|
+
return DONE;
|
|
20447
|
+
}
|
|
20129
20448
|
const target = await this.lookup(parsed2.user);
|
|
20130
20449
|
if (!target) return failed2("ENOENT", `sudo: unknown user ${parsed2.user}`);
|
|
20131
20450
|
const root = cred.euid === 0;
|
|
20132
|
-
const rules = root ? [] : policy;
|
|
20133
|
-
const
|
|
20134
|
-
if (parsed2.list) return this.listed(self, root,
|
|
20451
|
+
const rules = root ? [] : policy.rules;
|
|
20452
|
+
const mine = rules.filter((rule) => applies(rule.who));
|
|
20453
|
+
if (parsed2.list) return this.listed(self, root, mine, settings.matching);
|
|
20454
|
+
const session = { caller, self, target, owner, policy: policyOf(settings), parsed: parsed2, signal };
|
|
20455
|
+
if (parsed2.validate) return this.validate(session, root, mine, call.cwd);
|
|
20456
|
+
return this.command(session, root, mine, call);
|
|
20457
|
+
}
|
|
20458
|
+
async command(s, root, mine, call) {
|
|
20459
|
+
const { caller, self, target, parsed: parsed2 } = s;
|
|
20460
|
+
const { cred } = caller;
|
|
20135
20461
|
const [word = "", ...args] = parsed2.command;
|
|
20136
20462
|
const cwd = await this.canonical(call.cwd);
|
|
20137
20463
|
if (cwd === void 0 || !await this.reachable(cred, cwd)) {
|
|
@@ -20145,8 +20471,8 @@ var Sudo = class {
|
|
|
20145
20471
|
const untrusted2 = await this.untrusted(path, cwd, word);
|
|
20146
20472
|
if (untrusted2) return untrusted2;
|
|
20147
20473
|
const argv = [path, ...args];
|
|
20148
|
-
const matching =
|
|
20149
|
-
(rule) =>
|
|
20474
|
+
const matching = mine.filter(
|
|
20475
|
+
(rule) => (rule.target === "ALL" || rule.target === target.name) && (rule.cwd === void 0 || rule.cwd === cwd) && stdinMatches(rule, caller.stdin) && commandMatches(rule.cmnd, argv)
|
|
20150
20476
|
);
|
|
20151
20477
|
const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term);
|
|
20152
20478
|
if (root || matching.some((rule) => rule.nopasswd)) return grant();
|
|
@@ -20157,23 +20483,67 @@ var Sudo = class {
|
|
|
20157
20483
|
`sudo: ${self.name} is not allowed to run ${quoted} as ${target.name}`
|
|
20158
20484
|
);
|
|
20159
20485
|
}
|
|
20486
|
+
if (!parsed2.reset && this.timestamps.current(s.owner, s.policy)) {
|
|
20487
|
+
this.timestamps.stamp(s.owner, s.policy);
|
|
20488
|
+
this.logged(s, cwd, argv);
|
|
20489
|
+
return grant();
|
|
20490
|
+
}
|
|
20160
20491
|
if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
|
|
20161
|
-
|
|
20162
|
-
|
|
20163
|
-
|
|
20164
|
-
const answer2 = await this.ask(caller, self, target, parsed2.command, path, cwd, signal);
|
|
20165
|
-
if (typeof answer2 !== "string") return answer2;
|
|
20492
|
+
return this.pendingFor(cred.ruid, async () => {
|
|
20493
|
+
const answer2 = await this.ask(s, { argv: parsed2.command, path, cwd });
|
|
20494
|
+
if ("error" in answer2) return answer2;
|
|
20166
20495
|
if (await this.canonical(call.cwd) !== cwd || !await this.reachable(cred, cwd)) {
|
|
20167
20496
|
return failed2("EACCES", `sudo: ${call.cwd} changed while the request waited`);
|
|
20168
20497
|
}
|
|
20169
20498
|
const changed = await this.untrusted(path, cwd, word);
|
|
20170
20499
|
if (changed) return changed;
|
|
20171
|
-
|
|
20172
|
-
|
|
20500
|
+
const result = answer2.answer === "once" ? grant() : await this.always(self, target, cwd, argv, recordable(cwd, parsed2.command), grant);
|
|
20501
|
+
if (!("error" in result)) this.stamped(s, answer2);
|
|
20502
|
+
return result;
|
|
20503
|
+
});
|
|
20504
|
+
}
|
|
20505
|
+
async validate(s, root, mine, dir) {
|
|
20506
|
+
const { self, parsed: parsed2 } = s;
|
|
20507
|
+
if (root) return DONE;
|
|
20508
|
+
if (mine.length === 0) {
|
|
20509
|
+
return failed2("EPERM", `Sorry, user ${self.name} may not run sudo on ${this.o.hostname}.`);
|
|
20510
|
+
}
|
|
20511
|
+
if (mine.every((rule) => rule.nopasswd)) return DONE;
|
|
20512
|
+
if (!parsed2.reset && this.timestamps.current(s.owner, s.policy)) {
|
|
20513
|
+
this.timestamps.stamp(s.owner, s.policy);
|
|
20514
|
+
return DONE;
|
|
20515
|
+
}
|
|
20516
|
+
if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
|
|
20517
|
+
const cwd = await this.canonical(dir) ?? normalizePath(dir);
|
|
20518
|
+
return this.pendingFor(s.caller.cred.ruid, async () => {
|
|
20519
|
+
const answer2 = await this.ask(s, { argv: [], path: "", cwd, validate: true });
|
|
20520
|
+
if ("error" in answer2) return answer2;
|
|
20521
|
+
if (!answer2.person) await s.caller.report?.(NOT_STAMPED);
|
|
20522
|
+
this.stamped(s, answer2);
|
|
20523
|
+
return DONE;
|
|
20524
|
+
});
|
|
20525
|
+
}
|
|
20526
|
+
async pendingFor(uid, work) {
|
|
20527
|
+
if (this.pending.has(uid)) return failed2("EBUSY", "sudo: another request is pending");
|
|
20528
|
+
this.pending.add(uid);
|
|
20529
|
+
try {
|
|
20530
|
+
return await work();
|
|
20173
20531
|
} finally {
|
|
20174
|
-
this.pending.delete(
|
|
20532
|
+
this.pending.delete(uid);
|
|
20175
20533
|
}
|
|
20176
20534
|
}
|
|
20535
|
+
stamped(s, answer2) {
|
|
20536
|
+
if (answer2.person && !s.parsed.reset) this.timestamps.stamp(s.owner, s.policy);
|
|
20537
|
+
}
|
|
20538
|
+
logged(s, cwd, argv) {
|
|
20539
|
+
const { caller, self, target, owner, policy } = s;
|
|
20540
|
+
const type = policy.type === "tty" && !owner.terminal ? "ppid" : policy.type;
|
|
20541
|
+
const tty = owner.terminal?.tty ?? caller.tty ?? "unknown";
|
|
20542
|
+
const command = argv.map(quoteWord).join(" ");
|
|
20543
|
+
(this.o.warn ?? console.warn)(
|
|
20544
|
+
`sudo: ${self.name} : TTY=${tty} ; PWD=${quoteWord(cwd)} ; USER=${target.name} ; TIMESTAMP=${type} ; COMMAND=${command}`
|
|
20545
|
+
);
|
|
20546
|
+
}
|
|
20177
20547
|
async always(self, target, cwd, argv, shown2, grant) {
|
|
20178
20548
|
if (!shown2 || !recordable(cwd, argv)) return { ...grant(), notice: RAN_ONCE };
|
|
20179
20549
|
if (!await this.remember(self, target, cwd, argv)) {
|
|
@@ -20181,7 +20551,7 @@ var Sudo = class {
|
|
|
20181
20551
|
}
|
|
20182
20552
|
return grant();
|
|
20183
20553
|
}
|
|
20184
|
-
listed(self, root, rules) {
|
|
20554
|
+
listed(self, root, rules, defaults2) {
|
|
20185
20555
|
const host = this.o.hostname;
|
|
20186
20556
|
if (root) {
|
|
20187
20557
|
return {
|
|
@@ -20194,11 +20564,20 @@ var Sudo = class {
|
|
|
20194
20564
|
return failed2("EPERM", `User ${self.name} is not allowed to run sudo on ${host}.`);
|
|
20195
20565
|
}
|
|
20196
20566
|
const lines2 = rules.map(described).join("\n");
|
|
20197
|
-
|
|
20567
|
+
const params = defaults2.flatMap((d) => d.params).join(", ");
|
|
20568
|
+
const matching = params === "" ? "" : `Matching Defaults entries for ${self.name} on ${host}:
|
|
20569
|
+
${params}
|
|
20570
|
+
|
|
20571
|
+
`;
|
|
20572
|
+
return {
|
|
20573
|
+
list: `${matching}User ${self.name} may run the following commands on ${host}:
|
|
20198
20574
|
${lines2}
|
|
20199
|
-
`
|
|
20575
|
+
`
|
|
20576
|
+
};
|
|
20200
20577
|
}
|
|
20201
|
-
async ask(
|
|
20578
|
+
async ask(s, asked) {
|
|
20579
|
+
const { caller, self, target, signal: requester } = s;
|
|
20580
|
+
const { argv, path, cwd } = asked;
|
|
20202
20581
|
const { approve } = this.o;
|
|
20203
20582
|
if (!approve || requester.aborted) return failed2("EACCES", DENIED2);
|
|
20204
20583
|
const createdAt = Date.now();
|
|
@@ -20217,9 +20596,11 @@ ${lines2}
|
|
|
20217
20596
|
...caller.tty ? { tty: caller.tty } : {},
|
|
20218
20597
|
createdAt,
|
|
20219
20598
|
expiresAt: createdAt + timeout,
|
|
20220
|
-
always: recordable(cwd, [path, ...argv]),
|
|
20599
|
+
always: asked.validate ? false : recordable(cwd, [path, ...argv]),
|
|
20221
20600
|
stdin: caller.stdin,
|
|
20222
|
-
rootOnly: await this.rootOnly(cwd, argv.slice(1))
|
|
20601
|
+
rootOnly: asked.validate ? {} : await this.rootOnly(cwd, argv.slice(1)),
|
|
20602
|
+
...asked.validate ? { validate: true } : {},
|
|
20603
|
+
timestamp: true
|
|
20223
20604
|
};
|
|
20224
20605
|
const control = new AbortController();
|
|
20225
20606
|
const timer = setTimeout(() => control.abort("expired"), timeout);
|
|
@@ -20229,9 +20610,8 @@ ${lines2}
|
|
|
20229
20610
|
try {
|
|
20230
20611
|
const answer2 = await answerOf(approve, req, control.signal);
|
|
20231
20612
|
if (answer2 === ABORTED) return ended();
|
|
20232
|
-
|
|
20233
|
-
|
|
20234
|
-
}
|
|
20613
|
+
const approved = approvalOf(answer2);
|
|
20614
|
+
if (approved) return control.signal.aborted ? ended() : approved;
|
|
20235
20615
|
return failed2("EACCES", denialText(answer2));
|
|
20236
20616
|
} finally {
|
|
20237
20617
|
clearTimeout(timer);
|
|
@@ -20241,7 +20621,7 @@ ${lines2}
|
|
|
20241
20621
|
async remember(self, target, cwd, argv) {
|
|
20242
20622
|
const { fs } = this.o;
|
|
20243
20623
|
const path = `${SUDOERS_D}/${self.name}`;
|
|
20244
|
-
if (!
|
|
20624
|
+
if (!NAME4.test(self.name)) return false;
|
|
20245
20625
|
try {
|
|
20246
20626
|
await this.provision();
|
|
20247
20627
|
if (!await this.trusted(SUDOERS_D)) return false;
|
|
@@ -21610,8 +21990,8 @@ function sameCert(a, b) {
|
|
|
21610
21990
|
if (!a || !b) return a === b;
|
|
21611
21991
|
return a.cert.length === b.cert.length && a.cert.every((byte, i) => byte === b.cert[i]);
|
|
21612
21992
|
}
|
|
21613
|
-
function winner(current, expected,
|
|
21614
|
-
return sameCert(current, expected) ?
|
|
21993
|
+
function winner(current, expected, record3) {
|
|
21994
|
+
return sameCert(current, expected) ? record3 : current;
|
|
21615
21995
|
}
|
|
21616
21996
|
var DAY = 24 * 60 * 60 * 1e3;
|
|
21617
21997
|
var CA_LIFETIME = 3650 * DAY;
|
|
@@ -21644,15 +22024,15 @@ function indexedDbCaStore(dbName = "slicc-realm-ca") {
|
|
|
21644
22024
|
const store = (await open2()).transaction("ca").objectStore("ca");
|
|
21645
22025
|
return await request2(store.get(owner));
|
|
21646
22026
|
},
|
|
21647
|
-
async swap(owner, expected,
|
|
22027
|
+
async swap(owner, expected, record3) {
|
|
21648
22028
|
const tx = (await open2()).transaction("ca", "readwrite");
|
|
21649
22029
|
const store = tx.objectStore("ca");
|
|
21650
|
-
let stored2 =
|
|
22030
|
+
let stored2 = record3;
|
|
21651
22031
|
const read = store.get(owner);
|
|
21652
22032
|
read.onsuccess = () => {
|
|
21653
22033
|
const current = read.result;
|
|
21654
|
-
const kept = winner(current, expected,
|
|
21655
|
-
if (kept ===
|
|
22034
|
+
const kept = winner(current, expected, record3);
|
|
22035
|
+
if (kept === record3) store.put(record3, owner);
|
|
21656
22036
|
else stored2 = kept;
|
|
21657
22037
|
};
|
|
21658
22038
|
await new Promise((resolve2, reject) => {
|
|
@@ -21672,14 +22052,14 @@ function validLeafName(host) {
|
|
|
21672
22052
|
}
|
|
21673
22053
|
var RealmCa = class _RealmCa {
|
|
21674
22054
|
record;
|
|
21675
|
-
constructor(
|
|
21676
|
-
this.record =
|
|
22055
|
+
constructor(record3) {
|
|
22056
|
+
this.record = record3;
|
|
21677
22057
|
}
|
|
21678
22058
|
static async open(owner, store, now = Date.now()) {
|
|
21679
22059
|
const stored2 = await store.get(owner).catch(() => void 0);
|
|
21680
22060
|
if (stored2 && stored2.notAfter - now > CA_RENEW_BEFORE) return new _RealmCa(stored2);
|
|
21681
|
-
const
|
|
21682
|
-
return new _RealmCa(await store.swap(owner, stored2,
|
|
22061
|
+
const record3 = await _RealmCa.create(owner, now);
|
|
22062
|
+
return new _RealmCa(await store.swap(owner, stored2, record3));
|
|
21683
22063
|
}
|
|
21684
22064
|
static async create(owner, now) {
|
|
21685
22065
|
const pair = await crypto.subtle.generateKey(ECDSA, false, ["sign", "verify"]);
|
|
@@ -21906,8 +22286,8 @@ function memoryCaStore() {
|
|
|
21906
22286
|
const records = /* @__PURE__ */ new Map();
|
|
21907
22287
|
return {
|
|
21908
22288
|
get: async (owner) => records.get(owner),
|
|
21909
|
-
swap: async (owner, expected,
|
|
21910
|
-
const kept = winner(records.get(owner), expected,
|
|
22289
|
+
swap: async (owner, expected, record3) => {
|
|
22290
|
+
const kept = winner(records.get(owner), expected, record3);
|
|
21911
22291
|
records.set(owner, kept);
|
|
21912
22292
|
return kept;
|
|
21913
22293
|
}
|
|
@@ -23948,7 +24328,7 @@ function replaced(text, values) {
|
|
|
23948
24328
|
for (const [ph, value2] of Object.entries(values)) out = out.split(ph).join(binary(value2));
|
|
23949
24329
|
return out;
|
|
23950
24330
|
}
|
|
23951
|
-
function
|
|
24331
|
+
function usable2(values) {
|
|
23952
24332
|
const out = {};
|
|
23953
24333
|
for (const [ph, value2] of Object.entries(values ?? {}))
|
|
23954
24334
|
if (encoder8.encode(value2).length >= MIN_VALUE) out[ph] = value2;
|
|
@@ -23963,7 +24343,7 @@ async function substituteHead(head, ask) {
|
|
|
23963
24343
|
if (!request3 || !found || found.length !== 1) return void 0;
|
|
23964
24344
|
const at = found[0];
|
|
23965
24345
|
if (at.placeholders.length > SESSION_PLACEHOLDERS) return void 0;
|
|
23966
|
-
const values =
|
|
24346
|
+
const values = usable2(
|
|
23967
24347
|
await ask(`header:${at.name}`, at.placeholders, {
|
|
23968
24348
|
method: request3[1],
|
|
23969
24349
|
path: request3[2]
|
|
@@ -24273,7 +24653,7 @@ async function cleartext(message3, asks) {
|
|
|
24273
24653
|
const placeholders = placeholdersIn(password);
|
|
24274
24654
|
if (placeholders.length === 0 || placeholders.length > SESSION_PLACEHOLDERS)
|
|
24275
24655
|
return { bytes: message3, done: true };
|
|
24276
|
-
const values =
|
|
24656
|
+
const values = usable2(await asks.cleartext(placeholders));
|
|
24277
24657
|
if (Object.keys(values).length === 0) return { bytes: message3, done: true };
|
|
24278
24658
|
return { bytes: frame3(112, `${replaced(password, values)}\0`), values, done: true };
|
|
24279
24659
|
}
|
|
@@ -24338,7 +24718,7 @@ var PgClient = class {
|
|
|
24338
24718
|
const x = got.exchange;
|
|
24339
24719
|
const binding = this.header === END_POINT ? this.server.binding : void 0;
|
|
24340
24720
|
const serverFinal = `c=${channelOf(this.header, binding)},r=${got.nonce}`;
|
|
24341
|
-
const values =
|
|
24721
|
+
const values = usable2(
|
|
24342
24722
|
await this.asks.scram({
|
|
24343
24723
|
clientFirstBare: x.clientFirstBare,
|
|
24344
24724
|
serverFirst: x.serverFirst,
|
|
@@ -24387,7 +24767,7 @@ function decoded(b642) {
|
|
|
24387
24767
|
async function swapped2(text, position, ask) {
|
|
24388
24768
|
const placeholders = placeholdersIn(text);
|
|
24389
24769
|
if (placeholders.length === 0 || placeholders.length > SESSION_PLACEHOLDERS) return void 0;
|
|
24390
|
-
const values =
|
|
24770
|
+
const values = usable2(await ask(position, placeholders));
|
|
24391
24771
|
if (Object.keys(values).length === 0) return void 0;
|
|
24392
24772
|
return { text: replaced(text, values), values };
|
|
24393
24773
|
}
|
|
@@ -30156,7 +30536,7 @@ function header2(id2, flags2, counts) {
|
|
|
30156
30536
|
function opt() {
|
|
30157
30537
|
return [0, 0, TYPE_OPT, UDP_EDNS >> 8, UDP_EDNS & 255, 0, 0, 0, 0, 0, 0];
|
|
30158
30538
|
}
|
|
30159
|
-
function
|
|
30539
|
+
function record2(type, rdata) {
|
|
30160
30540
|
const ttl = DNS_STUB_TTL;
|
|
30161
30541
|
return [
|
|
30162
30542
|
192,
|
|
@@ -30264,14 +30644,14 @@ var DnsStub = class {
|
|
|
30264
30644
|
const address = reverseAddress(q.name);
|
|
30265
30645
|
const name2 = address !== void 0 ? await this.reverse(address) : void 0;
|
|
30266
30646
|
if (name2 !== void 0)
|
|
30267
|
-
return answerReply(query, q, NOERROR, [
|
|
30647
|
+
return answerReply(query, q, NOERROR, [record2(TYPE_PTR, encodeName(name2))], true);
|
|
30268
30648
|
return this.forward(query, q);
|
|
30269
30649
|
}
|
|
30270
30650
|
if (q.type === TYPE_A || q.type === TYPE_AAAA) {
|
|
30271
30651
|
const addresses = q.name === "" ? [] : await this.lookup(q.name);
|
|
30272
30652
|
const answers2 = addresses.flatMap((a) => {
|
|
30273
30653
|
const data = addressData(a, q.type);
|
|
30274
|
-
return data ? [
|
|
30654
|
+
return data ? [record2(q.type, data)] : [];
|
|
30275
30655
|
});
|
|
30276
30656
|
if (answers2.length > 0 || local(q.name) || await this.known(q.name)) {
|
|
30277
30657
|
const rcode = addresses.length > 0 ? NOERROR : NXDOMAIN;
|
|
@@ -33034,6 +33414,7 @@ var SUDO_SOURCE = [
|
|
|
33034
33414
|
" await say(1, answer.list);",
|
|
33035
33415
|
" return 0;",
|
|
33036
33416
|
" }",
|
|
33417
|
+
" if (answer.done === true) return 0;",
|
|
33037
33418
|
" if (answer.run !== undefined) {",
|
|
33038
33419
|
" try {",
|
|
33039
33420
|
" pid = (await ctx.spawn({ argv: answer.run, pgid: 0 })).pid;",
|
|
@@ -33673,6 +34054,7 @@ var Launcher = class {
|
|
|
33673
34054
|
);
|
|
33674
34055
|
}
|
|
33675
34056
|
this.users = new UserDb(this.fs, this.owners);
|
|
34057
|
+
const { sudoClock } = options;
|
|
33676
34058
|
this.sudoer = new Sudo({
|
|
33677
34059
|
fs: this.fs,
|
|
33678
34060
|
users: this.users,
|
|
@@ -33682,7 +34064,8 @@ var Launcher = class {
|
|
|
33682
34064
|
pnpmHome: this.pnpmHome,
|
|
33683
34065
|
backing: (path, cwd) => this.backing(path, cwd),
|
|
33684
34066
|
approve: options.approve,
|
|
33685
|
-
timeoutMs: options.sudoTimeoutMs
|
|
34067
|
+
timeoutMs: options.sudoTimeoutMs,
|
|
34068
|
+
now: sudoClock ? () => sudoClock.now() : void 0
|
|
33686
34069
|
});
|
|
33687
34070
|
this.cron = this.cronService(options.cron);
|
|
33688
34071
|
void this.unlinked.sweep("/");
|
|
@@ -34253,8 +34636,8 @@ var Launcher = class {
|
|
|
34253
34636
|
const caller = this.jobs.shown(ppid);
|
|
34254
34637
|
const named = typeof req.argv0 === "string" && !planned.script && !planned.target.fixed;
|
|
34255
34638
|
const argv = named ? [req.argv0, ...req.argv.slice(1)] : req.argv;
|
|
34256
|
-
const
|
|
34257
|
-
if (!
|
|
34639
|
+
const exact2 = booked?.pid === caller && req.file === booked.path && await physicalPathAsync(this.base, req.cwd) === booked.cwd && await argvDigest(argv) === booked.argv;
|
|
34640
|
+
if (!exact2) throw new SpawnError("EACCES");
|
|
34258
34641
|
const { requested: path, requestedCwd: cwd, path: resolved2 } = booked;
|
|
34259
34642
|
const pid = this.nextPid++;
|
|
34260
34643
|
const ids = { pid: req.exec ? caller : pid, ppid: caller };
|
|
@@ -35088,11 +35471,26 @@ var Launcher = class {
|
|
|
35088
35471
|
pid: this.jobs.shown(pid),
|
|
35089
35472
|
tags: this.tagsOf(pid),
|
|
35090
35473
|
tty: this.described.get(pid)?.tty,
|
|
35474
|
+
...this.timestampOwner(pid),
|
|
35091
35475
|
stdin,
|
|
35092
35476
|
report
|
|
35093
35477
|
};
|
|
35094
35478
|
return this.sudoer.request(caller, call, signal);
|
|
35095
35479
|
}
|
|
35480
|
+
timestampOwner(pid) {
|
|
35481
|
+
const sid = this.jobs.sidOf(pid);
|
|
35482
|
+
const tty = this.jobs.controllingTerminal(pid)?.name;
|
|
35483
|
+
const leader = sid === void 0 ? void 0 : this.startedOf(sid);
|
|
35484
|
+
const host = this.jobs.shownParent(pid) ?? this.jobs.shown(pid);
|
|
35485
|
+
const started = this.startedOf(host);
|
|
35486
|
+
return {
|
|
35487
|
+
...tty && sid !== void 0 && leader !== void 0 ? { terminal: { tty, sid, started: leader } } : {},
|
|
35488
|
+
...started === void 0 ? {} : { parent: { pid: host, started } }
|
|
35489
|
+
};
|
|
35490
|
+
}
|
|
35491
|
+
startedOf(shown2) {
|
|
35492
|
+
return this.running(shown2) ? this.described.get(shown2)?.started : void 0;
|
|
35493
|
+
}
|
|
35096
35494
|
visudo(pid, call) {
|
|
35097
35495
|
return this.sudoer.check(this.credOf(pid), call);
|
|
35098
35496
|
}
|
|
@@ -35602,9 +36000,9 @@ var Launcher = class {
|
|
|
35602
36000
|
}
|
|
35603
36001
|
pnpmReady() {
|
|
35604
36002
|
if (this.pnpmMade !== void 0) return this.pnpmMade;
|
|
35605
|
-
const
|
|
36003
|
+
const epoch2 = this.pnpmEpoch;
|
|
35606
36004
|
const made2 = this.base.mkdir(this.pnpmHome, { recursive: true }).then(() => this.owners ? this.stampLayout() : void 0).catch(() => {
|
|
35607
|
-
if (this.pnpmEpoch ===
|
|
36005
|
+
if (this.pnpmEpoch === epoch2) this.pnpmMade = void 0;
|
|
35608
36006
|
});
|
|
35609
36007
|
this.pnpmMade = made2;
|
|
35610
36008
|
return made2;
|