@poe-platform/safe-bash 0.1.773 → 0.1.774

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.
Files changed (31) hide show
  1. package/dist/image-ast/index.browser.js +742 -150
  2. package/dist/image-ast/index.js +742 -150
  3. package/dist/pdf-ast/index.js +742 -150
  4. package/dist/pdf-ast/vendor/pdfjs-fonts.d.mts +12 -0
  5. package/dist/pdf-ast/vendor/pdfjs-fonts.mjs +702 -0
  6. package/dist/safe-bash/command-chunks/{chunk-5VXZSNM2.js → chunk-4ZHDCMQX.js} +2 -2
  7. package/dist/safe-bash/command-chunks/{chunk-G3NVK25P.js → chunk-HH5QPKIN.js} +1 -1
  8. package/dist/safe-bash/command-chunks/{chunk-B2MM5DIT.js → chunk-PM63UJE6.js} +742 -150
  9. package/dist/safe-bash/core.browser.js +63 -7
  10. package/dist/safe-bash/shell/runtime.js +17 -9
  11. package/dist/safe-bash/shell/sync-posix-regex.d.ts +3 -0
  12. package/dist/safe-bash/shell/sync-posix-regex.js +48 -0
  13. package/dist/safe-bash-command-ffmpeg/index.js +7250 -7046
  14. package/dist/safe-bash-command-gpg/index.js +28 -7
  15. package/dist/safe-bash-command-imagemagick/index.js +2 -2
  16. package/dist/safe-bash-command-openssl/index.js +24 -6
  17. package/dist/safe-bash-command-pandoc/command.js +3 -3
  18. package/dist/safe-bash-command-pandoc/index.js +3 -3
  19. package/dist/safe-bash-command-pdfimages/index.js +742 -150
  20. package/dist/safe-bash-command-pdfinfo/index.js +742 -150
  21. package/dist/safe-bash-command-pdftk/index.js +742 -150
  22. package/dist/safe-bash-command-pdftoppm/index.js +742 -150
  23. package/dist/safe-bash-command-pdftotext/index.js +742 -150
  24. package/dist/safe-bash-command-qpdf/index.js +742 -150
  25. package/dist/safe-bash-command-sips/index.js +2 -2
  26. package/dist/safe-bash-command-soffice/index.js +1 -1
  27. package/dist/safe-bash-command-ssconvert/index.js +742 -150
  28. package/dist/safe-bash-command-ssh/index.js +124 -23
  29. package/dist/safe-bash-command-wkhtmltopdf/index.js +748 -150
  30. package/package.json +2 -2
  31. package/third-party/pdf-ast/THIRD_PARTY_NOTICES.md +22 -0
@@ -4,7 +4,7 @@ import {
4
4
 
5
5
  // packages/safe-bash-command-ssh/dist/index.js
6
6
  init_buffer();
7
- import { commandRuntimeIdentity, getCommandArguments, readBytes, writeText } from "../safe-bash-contracts/index.js";
7
+ import { FsError, commandRuntimeIdentity, getCommandArguments, readBytes, writeText } from "../safe-bash-contracts/index.js";
8
8
  import { PublicDiagnostic } from "../safe-bash-contracts/diagnostics.js";
9
9
  import { writeFileOutput } from "../safe-bash-contracts/filesystem-output-budget.js";
10
10
  import { yieldTurn } from "../safe-bash-contracts/yield.js";
@@ -216,6 +216,78 @@ function parseOpenSshEd25519PublicKey(line) {
216
216
  comment: parts.slice(idx + 2).join(" ")
217
217
  };
218
218
  }
219
+ function matchesGlob(value, pattern) {
220
+ let v = 0;
221
+ let p = 0;
222
+ let star = -1;
223
+ let retry = 0;
224
+ while (v < value.length) {
225
+ if (pattern[p] === "?" || pattern[p] === value[v]) {
226
+ v++;
227
+ p++;
228
+ } else if (pattern[p] === "*") {
229
+ star = p++;
230
+ retry = v;
231
+ } else if (star >= 0) {
232
+ p = star + 1;
233
+ v = ++retry;
234
+ } else
235
+ return false;
236
+ }
237
+ while (pattern[p] === "*")
238
+ p++;
239
+ return p === pattern.length;
240
+ }
241
+ function matchesPatterns(value, patterns) {
242
+ let matched = false;
243
+ for (const pattern of patterns.split(",")) {
244
+ const negative = pattern.startsWith("!");
245
+ if (matchesGlob(value, negative ? pattern.slice(1) : pattern)) {
246
+ if (negative)
247
+ return false;
248
+ matched = true;
249
+ }
250
+ }
251
+ return matched;
252
+ }
253
+ function parseAllowedSigner(line) {
254
+ const text = line.trim();
255
+ if (!text || text.startsWith("#"))
256
+ return void 0;
257
+ const fields = [];
258
+ let field = "";
259
+ let quoted = false;
260
+ for (const char of text) {
261
+ if (char === '"')
262
+ quoted = !quoted;
263
+ if (!quoted && (char === " " || char === " ")) {
264
+ if (field) {
265
+ fields.push(field);
266
+ field = "";
267
+ }
268
+ } else
269
+ field += char;
270
+ }
271
+ if (quoted)
272
+ return void 0;
273
+ if (field)
274
+ fields.push(field);
275
+ const principals = fields[0];
276
+ let index = 1;
277
+ let namespaces;
278
+ if (fields[index] !== "ssh-ed25519") {
279
+ const options = fields[index++];
280
+ if (!options?.startsWith('namespaces="') || !options.endsWith('"'))
281
+ return void 0;
282
+ namespaces = options.slice(12, -1);
283
+ if (!namespaces || namespaces.includes('"'))
284
+ return void 0;
285
+ }
286
+ if (!principals || fields[index] !== "ssh-ed25519" || !fields[index + 1])
287
+ return void 0;
288
+ const key = parseOpenSshEd25519PublicKey(`${fields[index]} ${fields[index + 1]}`);
289
+ return key ? { principals, ...namespaces === void 0 ? {} : { namespaces }, pubKey: key.pubKey } : void 0;
290
+ }
219
291
  async function computeFingerprintSha256(pubKey) {
220
292
  const blob = sshEd25519PubkeyBlob(pubKey);
221
293
  const buf = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
@@ -262,6 +334,20 @@ async function collectSourceBytes(source, maxBytes, signal) {
262
334
  }
263
335
  return result;
264
336
  }
337
+ async function readLimitedFile(context, path, maxBytes) {
338
+ try {
339
+ const bytes = await context.fs.readFile(path, { ...maxBytes === Infinity ? {} : { maxBytes }, signal: context.signal });
340
+ if (bytes.byteLength > maxBytes) {
341
+ throw new PublicDiagnostic(`input exceeds maximum buffered size of ${maxBytes} bytes`);
342
+ }
343
+ return bytes;
344
+ } catch (error) {
345
+ if (error instanceof FsError && error.code === "EFBIG") {
346
+ throw new PublicDiagnostic(`input exceeds maximum buffered size of ${maxBytes} bytes`);
347
+ }
348
+ throw error;
349
+ }
350
+ }
265
351
  function createSshKeygenCommand(options = {}) {
266
352
  const limits = settings(options);
267
353
  return {
@@ -280,6 +366,7 @@ function createSshKeygenCommand(options = {}) {
280
366
  let removeHost;
281
367
  let yAction;
282
368
  let namespace = "file";
369
+ let namespaceProvided = false;
283
370
  let allowedSignersFile;
284
371
  let signerIdentity;
285
372
  let signatureFile;
@@ -304,9 +391,10 @@ function createSshKeygenCommand(options = {}) {
304
391
  removeHost = args[++i];
305
392
  else if (a === "-Y" && i + 1 < args.length)
306
393
  yAction = args[++i];
307
- else if (a === "-n" && i + 1 < args.length)
394
+ else if (a === "-n" && i + 1 < args.length) {
308
395
  namespace = args[++i];
309
- else if (a === "-I" && i + 1 < args.length)
396
+ namespaceProvided = true;
397
+ } else if (a === "-I" && i + 1 < args.length)
310
398
  signerIdentity = args[++i];
311
399
  else if (a === "-s" && i + 1 < args.length)
312
400
  signatureFile = args[++i];
@@ -318,7 +406,11 @@ function createSshKeygenCommand(options = {}) {
318
406
  try {
319
407
  if (findHost !== void 0) {
320
408
  const khPath = pathPosix.resolve(context.cwd, fileArg ?? "/home/user/.ssh/known_hosts");
321
- const khText = textDecoder.decode(await context.fs.readFile(khPath).catch(() => new Uint8Array(0)));
409
+ const khText = textDecoder.decode(await readLimitedFile(context, khPath, maxBytes).catch((error) => {
410
+ if (error instanceof FsError && error.code === "ENOENT")
411
+ return new Uint8Array(0);
412
+ throw error;
413
+ }));
322
414
  const matches = khText.split("\n").filter((l) => l.trim().length > 0 && !l.startsWith("#") && l.split(/\s+/)[0]?.split(",").includes(findHost));
323
415
  if (matches.length === 0)
324
416
  return { exitCode: 1 };
@@ -328,7 +420,11 @@ function createSshKeygenCommand(options = {}) {
328
420
  }
329
421
  if (removeHost !== void 0) {
330
422
  const khPath = pathPosix.resolve(context.cwd, fileArg ?? "/home/user/.ssh/known_hosts");
331
- const khText = textDecoder.decode(await context.fs.readFile(khPath).catch(() => new Uint8Array(0)));
423
+ const khText = textDecoder.decode(await readLimitedFile(context, khPath, maxBytes).catch((error) => {
424
+ if (error instanceof FsError && error.code === "ENOENT")
425
+ return new Uint8Array(0);
426
+ throw error;
427
+ }));
332
428
  const remaining = khText.split("\n").filter((l) => l.trim().length === 0 || l.startsWith("#") || !l.split(/\s+/)[0]?.split(",").includes(removeHost));
333
429
  const outBytes = textEncoder.encode(`${remaining.join("\n")}
334
430
  `);
@@ -341,13 +437,13 @@ ${khPath} updated.
341
437
  if (yAction !== void 0) {
342
438
  if (yAction === "sign") {
343
439
  const keyPath = pathPosix.resolve(context.cwd, fileArg ?? "/home/user/.ssh/id_ed25519");
344
- const keyPem = textDecoder.decode(await context.fs.readFile(keyPath));
440
+ const keyPem = textDecoder.decode(await readLimitedFile(context, keyPath, maxBytes));
345
441
  const parsed = parseOpenSshEd25519PrivateKey(keyPem);
346
442
  if (!parsed)
347
443
  throw new PublicDiagnostic(`invalid OpenSSH Ed25519 key: ${keyPath}`);
348
444
  const { privateKey } = await importEd25519FromSeedAndPub(parsed.seed, parsed.pubKey);
349
445
  const targetFile = positionalFiles[0];
350
- const payload = targetFile ? await context.fs.readFile(pathPosix.resolve(context.cwd, targetFile)) : await collectSourceBytes(context.stdin, maxBytes, context.signal);
446
+ const payload = targetFile ? await readLimitedFile(context, pathPosix.resolve(context.cwd, targetFile), maxBytes) : await collectSourceBytes(context.stdin, maxBytes, context.signal);
351
447
  const payloadBuf = payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength);
352
448
  const msgHash = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-512", payloadBuf));
353
449
  const toSign = [];
@@ -389,7 +485,7 @@ Write signature to ${targetFile}.sig
389
485
  }
390
486
  if (yAction === "verify" || yAction === "check-novalidate" || yAction === "find-principals") {
391
487
  const sigPath = pathPosix.resolve(context.cwd, signatureFile ?? positionalFiles[0] ?? "");
392
- const sigPem = textDecoder.decode(await context.fs.readFile(sigPath));
488
+ const sigPem = textDecoder.decode(await readLimitedFile(context, sigPath, maxBytes));
393
489
  const b64 = sigPem.split("\n").map((l) => l.trim()).filter((l) => !l.startsWith("-----") && l.length > 0).join("");
394
490
  const raw = base64ToBytes(b64);
395
491
  if (raw.byteLength < 10 || textDecoder.decode(raw.slice(0, 6)) !== "SSHSIG") {
@@ -412,22 +508,27 @@ Write signature to ${targetFile}.sig
412
508
  const rawSig = readSshString(sigBlob, spos);
413
509
  if (!pubKey2 || !rawSig)
414
510
  throw new PublicDiagnostic("invalid SSHSIG key/signature");
415
- if (yAction === "find-principals") {
511
+ if (yAction !== "find-principals" && (!namespaceProvided || textDecoder.decode(sigNs) !== namespace)) {
512
+ throw new PublicDiagnostic("signature namespace does not match requested namespace");
513
+ }
514
+ if (yAction === "verify" || yAction === "find-principals") {
416
515
  allowedSignersFile = fileArg;
417
- if (allowedSignersFile) {
418
- const asText = textDecoder.decode(await context.fs.readFile(pathPosix.resolve(context.cwd, allowedSignersFile)));
419
- const fp3 = await computeFingerprintSha256(pubKey2);
420
- for (const line of asText.split("\n")) {
421
- const p = parseOpenSshEd25519PublicKey(line);
422
- if (p && await computeFingerprintSha256(p.pubKey) === fp3) {
423
- const principal = line.trim().split(/\s+/)[0];
424
- await writeText(context.stdout, `${principal}
516
+ if (!allowedSignersFile || yAction === "verify" && !signerIdentity) {
517
+ throw new PublicDiagnostic("allowed signers file and signer identity are required for verification");
518
+ }
519
+ const asText = textDecoder.decode(await readLimitedFile(context, pathPosix.resolve(context.cwd, allowedSignersFile), maxBytes));
520
+ const entries = asText.split("\n").map(parseAllowedSigner).filter((entry) => entry !== void 0);
521
+ const matching = entries.filter((entry) => entry.pubKey.every((byte, index) => byte === pubKey2[index]) && (yAction === "find-principals" || matchesPatterns(namespace, entry.namespaces ?? "*")));
522
+ if (yAction === "find-principals") {
523
+ if (matching.length === 0)
524
+ return { exitCode: 1 };
525
+ await writeText(context.stdout, `${matching.map((entry) => entry.principals).join("\n")}
425
526
  `);
426
- return { exitCode: 0 };
427
- }
428
- }
527
+ return { exitCode: 0 };
528
+ }
529
+ if (!matching.some((entry) => matchesPatterns(signerIdentity, entry.principals))) {
530
+ throw new PublicDiagnostic("signer is not authorized by allowed signers");
429
531
  }
430
- return { exitCode: 1 };
431
532
  }
432
533
  const payload = await collectSourceBytes(context.stdin, maxBytes, context.signal);
433
534
  const payloadBuf = payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength);
@@ -457,7 +558,7 @@ Write signature to ${targetFile}.sig
457
558
  }
458
559
  if (derivePub) {
459
560
  const keyPath = pathPosix.resolve(context.cwd, fileArg ?? "/home/user/.ssh/id_ed25519");
460
- const pem = textDecoder.decode(await context.fs.readFile(keyPath));
561
+ const pem = textDecoder.decode(await readLimitedFile(context, keyPath, maxBytes));
461
562
  const parsed = parseOpenSshEd25519PrivateKey(pem);
462
563
  if (!parsed)
463
564
  throw new PublicDiagnostic(`invalid OpenSSH private key: ${keyPath}`);
@@ -468,7 +569,7 @@ Write signature to ${targetFile}.sig
468
569
  }
469
570
  if (showFingerprint) {
470
571
  const keyPath = pathPosix.resolve(context.cwd, fileArg ?? "/home/user/.ssh/id_ed25519.pub");
471
- const content = textDecoder.decode(await context.fs.readFile(keyPath));
572
+ const content = textDecoder.decode(await readLimitedFile(context, keyPath, maxBytes));
472
573
  const privParsed = parseOpenSshEd25519PrivateKey(content);
473
574
  const pubParsed = privParsed ?? parseOpenSshEd25519PublicKey(content);
474
575
  if (!pubParsed)