@peac/cli 0.14.1 → 0.14.2

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/index.mjs CHANGED
@@ -3,12 +3,12 @@ import { Command, CommanderError, Option } from 'commander';
3
3
  import { verifyReceipt, fetchIssuerConfig, parseIssuerConfig, issue, IssueError } from '@peac/protocol';
4
4
  import { generateKeypair, base64urlEncode, sign, decode, canonicalize, sha256Hex as sha256Hex$1, validateKeypair, base64urlDecode, derivePublicKey } from '@peac/crypto';
5
5
  import * as fs6 from 'fs';
6
- import { existsSync, readFileSync, writeFileSync, statSync, openSync, constants, closeSync, unlinkSync, createReadStream } from 'fs';
6
+ import { writeFileSync, existsSync, readFileSync, openSync, constants, fstatSync, closeSync, statSync, unlinkSync, createReadStream } from 'fs';
7
7
  import * as path6 from 'path';
8
8
  import { join, resolve, dirname, delimiter } from 'path';
9
9
  import { loadProfile, serializePolicyJson, serializePolicyYaml, ProfileError, createExamplePolicy, loadPolicy, PolicyValidationError, PolicyLoadError, compilePeacTxt, compileRobotsSnippet, compileAiprefTemplates, renderPolicyMarkdown, explainMatches, evaluate, listProfiles, getProfileSummary, parsePolicyDocument } from '@peac/policy-kit';
10
- import { execSync, spawn } from 'child_process';
11
10
  import { createHash, randomBytes } from 'crypto';
11
+ import { execSync, spawn } from 'child_process';
12
12
  import { LIFECYCLE_OBSERVATION_TYPE_URIS, computeReceiptRef, CLI_EXECUTION_EXTENSION_KEY, CLI_COMMAND_EXECUTION_TYPE, validateLifecycleObservation, LIFECYCLE_OBSERVATION_EXTENSION_KEY, validateWorkflowContextOrdered, isValidPurposeToken, isCanonicalPurpose, validateObligationsExtension, validateInteractionOrdered, validateDisputeAttestation, validateAttributionAttestation, validateAgentIdentityAttestation, ReceiptClaimsSchema, CliExecutionSchema, isCanonicalIss, parseReceiptClaims, OpaqueRefSchema } from '@peac/schema';
13
13
  import { POLICY } from '@peac/kernel';
14
14
  import { readDisputeBundle } from '@peac/audit';
@@ -16,6 +16,55 @@ import { createRequire } from 'module';
16
16
  import chalk from 'chalk';
17
17
  import { readFile } from 'fs/promises';
18
18
 
19
+ function readFileBufferSnapshot(path9, options) {
20
+ let fd;
21
+ try {
22
+ fd = openSync(path9, constants.O_RDONLY);
23
+ const stat = fstatSync(fd);
24
+ if (stat.isDirectory()) {
25
+ throw makeErrnoError("EISDIR", `is a directory: ${path9}`);
26
+ }
27
+ if (options?.maxBytes !== void 0 && stat.size > options.maxBytes) {
28
+ throw makeFileTooLargeError(path9, stat.size, options.maxBytes);
29
+ }
30
+ const data = readFileSync(fd);
31
+ if (options?.maxBytes !== void 0 && data.length > options.maxBytes) {
32
+ throw makeFileTooLargeError(path9, data.length, options.maxBytes);
33
+ }
34
+ return data;
35
+ } finally {
36
+ if (fd !== void 0) {
37
+ try {
38
+ closeSync(fd);
39
+ } catch {
40
+ }
41
+ }
42
+ }
43
+ }
44
+ function makeFileTooLargeError(path9, actualSize, maxBytes) {
45
+ const err = new Error(
46
+ `file size ${actualSize} bytes exceeds limit ${maxBytes} bytes: ${path9}`
47
+ );
48
+ err.code = "E_PEAC_FILE_TOO_LARGE";
49
+ err.actualSize = actualSize;
50
+ err.maxBytes = maxBytes;
51
+ return err;
52
+ }
53
+ function writeFileNoOverwrite(path9, data, options) {
54
+ const encoding = typeof data === "string" ? options?.encoding : null;
55
+ writeFileSync(path9, data, {
56
+ encoding,
57
+ flag: "wx",
58
+ mode: options?.mode
59
+ });
60
+ }
61
+ function makeErrnoError(code, message) {
62
+ const err = new Error(message);
63
+ err.code = code;
64
+ return err;
65
+ }
66
+
67
+ // src/commands/policy.ts
19
68
  function getGlobalOptions(cmd) {
20
69
  const parent = cmd.parent;
21
70
  if (!parent) return {};
@@ -40,14 +89,6 @@ policy.command("init").description("Create a new PEAC policy file").option("-f,
40
89
  try {
41
90
  const format = options.format?.toLowerCase() || "yaml";
42
91
  const outputPath = options.output || (format === "json" ? "peac-policy.json" : "peac-policy.yaml");
43
- if (fs6.existsSync(outputPath) && !options.force && !globalOpts.yes) {
44
- outputError(
45
- `File already exists: ${outputPath}`,
46
- { path: outputPath, hint: "Use --force or --yes to overwrite" },
47
- globalOpts
48
- );
49
- process.exit(1);
50
- }
51
92
  let content;
52
93
  let policyName;
53
94
  if (options.profile) {
@@ -68,7 +109,24 @@ policy.command("init").description("Create a new PEAC policy file").option("-f,
68
109
  content = format === "json" ? serializePolicyJson(example) : serializePolicyYaml(example);
69
110
  policyName = "Example Policy";
70
111
  }
71
- fs6.writeFileSync(outputPath, content, "utf-8");
112
+ const allowOverwrite = options.force === true || globalOpts.yes === true;
113
+ try {
114
+ if (allowOverwrite) {
115
+ fs6.writeFileSync(outputPath, content, { encoding: "utf-8", flag: "w" });
116
+ } else {
117
+ writeFileNoOverwrite(outputPath, content, { encoding: "utf-8" });
118
+ }
119
+ } catch (err) {
120
+ if (err.code === "EEXIST") {
121
+ outputError(
122
+ `File already exists: ${outputPath}`,
123
+ { path: outputPath, hint: "Use --force or --yes to overwrite" },
124
+ globalOpts
125
+ );
126
+ process.exit(1);
127
+ }
128
+ throw err;
129
+ }
72
130
  if (globalOpts.json) {
73
131
  output(
74
132
  {
@@ -484,7 +542,6 @@ policy.command("show-profile").description("Show details of a specific policy pr
484
542
  const globalOpts = getGlobalOptions(cmd);
485
543
  try {
486
544
  const profile = loadProfile(id);
487
- const summary = getProfileSummary(id);
488
545
  if (globalOpts.json) {
489
546
  output(
490
547
  {
@@ -2359,20 +2416,37 @@ function findDiffFields(jwsA, jwsB) {
2359
2416
  return diffs.sort();
2360
2417
  }
2361
2418
  async function readBundle(bundlePath, label) {
2362
- if (!fs6.existsSync(bundlePath)) {
2363
- return { ok: false, error: `Bundle file not found: ${bundlePath}` };
2364
- }
2365
- const stat = fs6.statSync(bundlePath);
2366
- if (stat.size > MAX_BUNDLE_SIZE) {
2367
- return { ok: false, error: `Bundle ${label} exceeds 16 MB size limit (${stat.size} bytes)` };
2419
+ let resolved;
2420
+ try {
2421
+ resolved = fs6.realpathSync(bundlePath);
2422
+ } catch (err) {
2423
+ if (err.code === "ENOENT") {
2424
+ return { ok: false, error: `Bundle file not found: ${bundlePath}` };
2425
+ }
2426
+ return { ok: false, error: `Failed to read ${label}: ${err.message}` };
2368
2427
  }
2369
- const resolved = fs6.realpathSync(bundlePath);
2370
2428
  if (resolved !== bundlePath && !resolved.startsWith(process.cwd())) {
2371
- if (!fs6.existsSync(resolved)) {
2372
- return { ok: false, error: `Path traversal detected for ${label}` };
2429
+ return { ok: false, error: `Path traversal detected for ${label}` };
2430
+ }
2431
+ let zipBuffer;
2432
+ try {
2433
+ zipBuffer = readFileBufferSnapshot(bundlePath, { maxBytes: MAX_BUNDLE_SIZE });
2434
+ } catch (err) {
2435
+ const e = err;
2436
+ if (e.code === "ENOENT") {
2437
+ return { ok: false, error: `Bundle file not found: ${bundlePath}` };
2438
+ }
2439
+ if (e.code === "EISDIR") {
2440
+ return { ok: false, error: `Bundle ${label} is not a regular file: ${bundlePath}` };
2373
2441
  }
2442
+ if (e.code === "E_PEAC_FILE_TOO_LARGE") {
2443
+ return {
2444
+ ok: false,
2445
+ error: `Bundle ${label} exceeds 16 MB size limit (${e.actualSize ?? "unknown"} bytes)`
2446
+ };
2447
+ }
2448
+ return { ok: false, error: `Failed to read ${label}: ${err.message}` };
2374
2449
  }
2375
- const zipBuffer = fs6.readFileSync(bundlePath);
2376
2450
  const result = await readDisputeBundle(zipBuffer);
2377
2451
  if (!result.ok) {
2378
2452
  return { ok: false, error: `Failed to read ${label}: ${result.error.message}` };
@@ -4731,7 +4805,7 @@ program.command("verify").description("Verify a PEAC receipt JWS").argument("<jw
4731
4805
  jws = fs6.readFileSync(jwsInput, "utf-8").trim();
4732
4806
  }
4733
4807
  console.log("Verifying PEAC receipt...\n");
4734
- const { header, payload } = decode(jws);
4808
+ const { payload } = decode(jws);
4735
4809
  console.log("Receipt Information:");
4736
4810
  console.log(` Receipt ID: ${payload.rid}`);
4737
4811
  console.log(` Issuer: ${payload.iss}`);