@peac/cli 0.13.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -100,8 +100,8 @@ peac samples generate --output ./out # Generate sample files
100
100
  - `@peac/crypto` (Layer 2): JWS decoding and signature operations
101
101
  - `@peac/schema` (Layer 1): Receipt schema validation
102
102
  - `@peac/audit`: Evidence bundle reading for reconciliation
103
- - `@peac/disc`: Issuer discovery
104
103
  - `@peac/policy-kit`: Policy loading, validation, and compilation
104
+ - `@peac/net-node`: SSRF-aware HTTP client used by `peac discover`
105
105
 
106
106
  ## For Agent Developers
107
107
 
@@ -1,13 +1,16 @@
1
1
  /**
2
2
  * peac discover <url> command
3
- * Thin wrapper around @peac/disc for peac.txt policy-document discovery.
4
3
  *
5
- * peac.txt is a policy-document surface per docs/specs/PEAC-TXT.md.
6
- * For cryptographic key discovery, callers should use
4
+ * Performs SSRF-aware retrieval of /.well-known/peac.txt and parses it as a
5
+ * peac-policy/0.1 policy document. Implementation lives in the CLI-internal
6
+ * helper at ../lib/policy-document-discovery.ts (not a public surface).
7
+ *
8
+ * peac.txt is a policy-document surface per docs/specs/PEAC-TXT.md. For
9
+ * cryptographic key discovery, callers should use
7
10
  * /.well-known/peac-issuer.json (see docs/specs/PEAC-ISSUER.md).
8
11
  */
9
12
  import type { CLIOptions, CommandResult } from '../types.js';
10
13
  export declare class DiscoverCommand {
11
- execute(url: string, options?: CLIOptions): Promise<CommandResult>;
14
+ execute(url: string, _options?: CLIOptions): Promise<CommandResult>;
12
15
  }
13
16
  //# sourceMappingURL=discover.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../src/commands/discover.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG7D,qBAAa,eAAe;IACpB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,aAAa,CAAC;CA+B7E"}
1
+ {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../src/commands/discover.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAO7D,qBAAa,eAAe;IACpB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,UAAe,GAAG,OAAO,CAAC,aAAa,CAAC;CA0C9E"}
package/dist/index.cjs CHANGED
@@ -10,9 +10,12 @@ var policyKit = require('@peac/policy-kit');
10
10
  var child_process = require('child_process');
11
11
  var crypto$1 = require('crypto');
12
12
  var schema = require('@peac/schema');
13
- var disc = require('@peac/disc');
13
+ var kernel = require('@peac/kernel');
14
14
  var audit = require('@peac/audit');
15
15
  var module$1 = require('module');
16
+ var chalk = require('chalk');
17
+
18
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
16
19
 
17
20
  function _interopNamespace(e) {
18
21
  if (e && e.__esModule) return e;
@@ -34,6 +37,7 @@ function _interopNamespace(e) {
34
37
 
35
38
  var fs6__namespace = /*#__PURE__*/_interopNamespace(fs6);
36
39
  var path6__namespace = /*#__PURE__*/_interopNamespace(path6);
40
+ var chalk__default = /*#__PURE__*/_interopDefault(chalk);
37
41
 
38
42
  function getGlobalOptions(cmd) {
39
43
  const parent = cmd.parent;
@@ -703,7 +707,7 @@ var PROFILE_CAPABILITIES = {
703
707
  discovery: {
704
708
  profile: "discovery.config",
705
709
  level: "semantic",
706
- validator: "@peac/disc:parse"
710
+ validator: "@peac/policy-kit:parsePolicyDocument (via cli compat helper)"
707
711
  },
708
712
  dispute: {
709
713
  profile: "receipt.verify.dispute",
@@ -783,6 +787,124 @@ function shouldRunAtLevel(fixtureVersion, level) {
783
787
  const levelOrder = ["basic", "standard", "full"];
784
788
  return levelOrder.indexOf(fixtureLevel) <= levelOrder.indexOf(level);
785
789
  }
790
+ var DISCOVER_TIMEOUT_MS = 5e3;
791
+ var DISCOVER_MAX_BYTES = kernel.POLICY.maxBytes;
792
+ var PEAC_TXT_PATH = kernel.POLICY.manifestPath;
793
+ var DEFAULT_USER_AGENT = "peac-cli";
794
+ var LEGACY_KEY_LINE = /^\s*(verify|public_keys|jwks)\s*:/m;
795
+ var LEGACY_KEY_FULL_LINE_GLOBAL = /^\s*(verify|public_keys|jwks)\s*:.*\r?\n?/gm;
796
+ var legacyWarningFired = false;
797
+ function fireLegacyWarning(field) {
798
+ if (legacyWarningFired) return;
799
+ legacyWarningFired = true;
800
+ if (typeof process !== "undefined" && typeof process.emitWarning === "function") {
801
+ process.emitWarning(
802
+ `peac.txt legacy key-discovery field "${field}" is deprecated and ignored. peac.txt is a policy-document surface (docs/specs/PEAC-TXT.md). Key resolution uses iss -> /.well-known/peac-issuer.json -> jwks_uri -> JWKS.`,
803
+ { code: "PEAC_LEGACY_PEAC_TXT_KEY_FIELD", type: "DeprecationWarning" }
804
+ );
805
+ }
806
+ }
807
+ function collectLegacyWarnings(text, warnings) {
808
+ const lines = text.split(/\r?\n/);
809
+ let firstField = null;
810
+ lines.forEach((line, idx) => {
811
+ const m = line.match(/^\s*(verify|public_keys|jwks)\s*:/);
812
+ if (m) {
813
+ if (firstField === null) firstField = m[1];
814
+ warnings.push(
815
+ `Line ${idx + 1}: legacy key-discovery field "${m[1]}" ignored (peac.txt is policy-only; use peac-issuer.json for keys)`
816
+ );
817
+ }
818
+ });
819
+ if (firstField) fireLegacyWarning(firstField);
820
+ }
821
+ function failureFrom(err, warnings) {
822
+ const errors = [];
823
+ if (err instanceof policyKit.PolicyValidationError) {
824
+ const issues = err.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
825
+ errors.push(`Policy validation failed: ${issues}`);
826
+ } else if (err instanceof policyKit.PolicyLoadError) {
827
+ errors.push(err.message);
828
+ } else {
829
+ errors.push(`Unexpected parse error: ${err instanceof Error ? err.message : String(err)}`);
830
+ }
831
+ return {
832
+ valid: false,
833
+ errors,
834
+ warnings: warnings.length > 0 ? warnings : void 0
835
+ };
836
+ }
837
+ function parsePolicyDocumentCompat(text) {
838
+ if (text.trim().length === 0) {
839
+ return {
840
+ valid: false,
841
+ errors: ["Empty policy document. Expected peac-policy/0.1 YAML or JSON."]
842
+ };
843
+ }
844
+ const hasLegacyLines = LEGACY_KEY_LINE.test(text);
845
+ const warnings = [];
846
+ try {
847
+ const data = policyKit.parsePolicyDocument(text);
848
+ if (hasLegacyLines) collectLegacyWarnings(text, warnings);
849
+ return {
850
+ valid: true,
851
+ data,
852
+ warnings: warnings.length > 0 ? warnings : void 0
853
+ };
854
+ } catch (firstErr) {
855
+ if (!hasLegacyLines) return failureFrom(firstErr, warnings);
856
+ collectLegacyWarnings(text, warnings);
857
+ const stripped = text.replace(LEGACY_KEY_FULL_LINE_GLOBAL, "");
858
+ try {
859
+ const data = policyKit.parsePolicyDocument(stripped);
860
+ return { valid: true, data, warnings };
861
+ } catch (secondErr) {
862
+ return failureFrom(secondErr, warnings);
863
+ }
864
+ }
865
+ }
866
+ async function fetchPolicyDocumentText(origin, options = {}) {
867
+ let target;
868
+ try {
869
+ target = new URL(PEAC_TXT_PATH, origin);
870
+ } catch (err) {
871
+ return {
872
+ ok: false,
873
+ code: "INVALID_URL",
874
+ error: `Invalid origin: ${err instanceof Error ? err.message : String(err)}`
875
+ };
876
+ }
877
+ const fetchOptions = {
878
+ timeoutMs: options.timeoutMs ?? DISCOVER_TIMEOUT_MS,
879
+ maxResponseBytes: options.maxBytes ?? DISCOVER_MAX_BYTES,
880
+ headers: { "user-agent": options.userAgent ?? DEFAULT_USER_AGENT },
881
+ allowedMethods: ["GET"]
882
+ };
883
+ const { safeFetchRaw } = await import('@peac/net-node');
884
+ const raw = await safeFetchRaw(target.toString(), fetchOptions);
885
+ if (!raw.ok) {
886
+ return {
887
+ ok: false,
888
+ code: raw.code,
889
+ error: `${raw.code}: ${raw.error}`
890
+ };
891
+ }
892
+ try {
893
+ if (!raw.response.ok) {
894
+ return {
895
+ ok: false,
896
+ code: `HTTP_${raw.response.status}`,
897
+ error: `HTTP ${raw.response.status}: ${raw.response.statusText}`
898
+ };
899
+ }
900
+ const text = await raw.response.text();
901
+ return { ok: true, text, warnings: raw.warnings };
902
+ } finally {
903
+ await raw.close();
904
+ }
905
+ }
906
+
907
+ // src/lib/conformance/validators.ts
786
908
  function zodCodeToKeyword(code) {
787
909
  const mapping = {
788
910
  invalid_type: "type",
@@ -837,7 +959,7 @@ function validateParseReceiptClaims(payload) {
837
959
  }
838
960
  function validateDiscoveryInput(input) {
839
961
  if (typeof input === "string") {
840
- const result = disc.parse(input);
962
+ const result = parsePolicyDocumentCompat(input);
841
963
  if (result.valid) {
842
964
  return { valid: true };
843
965
  }
@@ -2593,6 +2715,118 @@ function doctor(program2) {
2593
2715
  process.exit(exitCode);
2594
2716
  });
2595
2717
  }
2718
+ function formatOutput(result, json = false) {
2719
+ if (json) {
2720
+ return JSON.stringify(result, null, 2);
2721
+ }
2722
+ if (!result.success) {
2723
+ return chalk__default.default.red(`Error: ${result.error}`);
2724
+ }
2725
+ if (result.data && typeof result.data === "object") {
2726
+ if (result.data.sources) {
2727
+ return formatDiscoveryResult(result.data);
2728
+ } else if (result.data.digest) {
2729
+ return formatHashResult(result.data);
2730
+ } else if (result.data.valid !== void 0) {
2731
+ return formatVerifyResult(result.data);
2732
+ }
2733
+ }
2734
+ return JSON.stringify(result.data, null, 2);
2735
+ }
2736
+ function formatDiscoveryResult(data) {
2737
+ const lines = [`Discovery for: ${chalk__default.default.blue(data.url)}`, ""];
2738
+ for (const source of data.sources) {
2739
+ const status = source.status === "found" ? chalk__default.default.green("[found]") : source.status === "not_found" ? chalk__default.default.yellow("[not found]") : chalk__default.default.red("[error]");
2740
+ lines.push(`${status} ${source.type}: ${source.url}`);
2741
+ if (source.etag) {
2742
+ lines.push(` ETag: ${source.etag}`);
2743
+ }
2744
+ }
2745
+ return lines.join("\\n");
2746
+ }
2747
+ function formatHashResult(data) {
2748
+ return [
2749
+ `Algorithm: ${data.algorithm}`,
2750
+ `Format: ${data.format}`,
2751
+ `Digest: ${chalk__default.default.green(data.digest)}`,
2752
+ `Input size: ${data.input_size} bytes`
2753
+ ].join("\\n");
2754
+ }
2755
+ function formatVerifyResult(data) {
2756
+ if (data.valid) {
2757
+ const lines = [chalk__default.default.green("Receipt verified successfully"), ""];
2758
+ if (data.policy_hash) {
2759
+ lines.push(`Policy hash: ${data.policy_hash}`);
2760
+ }
2761
+ if (data.resource) {
2762
+ lines.push(`Resource: ${data.resource}`);
2763
+ }
2764
+ return lines.join("\\n");
2765
+ } else {
2766
+ return chalk__default.default.red(`Verification failed: ${data.error || "Unknown error"}`);
2767
+ }
2768
+ }
2769
+ function handleError(error) {
2770
+ return {
2771
+ success: false,
2772
+ error: error.message
2773
+ };
2774
+ }
2775
+ function timing() {
2776
+ const started = Date.now();
2777
+ return {
2778
+ started,
2779
+ end: () => {
2780
+ const completed = Date.now();
2781
+ return {
2782
+ started,
2783
+ completed,
2784
+ duration: completed - started
2785
+ };
2786
+ }
2787
+ };
2788
+ }
2789
+
2790
+ // src/commands/discover.ts
2791
+ var DiscoverCommand = class {
2792
+ async execute(url, _options = {}) {
2793
+ const timer = timing();
2794
+ try {
2795
+ const parsedUrl = new URL(url);
2796
+ if (!["http:", "https:"].includes(parsedUrl.protocol)) {
2797
+ throw new Error("URL must use http: or https: protocol");
2798
+ }
2799
+ const fetched = await fetchPolicyDocumentText(url);
2800
+ if (!fetched.ok) {
2801
+ return {
2802
+ success: false,
2803
+ error: fetched.error,
2804
+ timing: timer.end()
2805
+ };
2806
+ }
2807
+ const parsed = parsePolicyDocumentCompat(fetched.text);
2808
+ const data = { ...parsed };
2809
+ if (fetched.warnings && fetched.warnings.length > 0) {
2810
+ data.warnings = [...parsed.warnings ?? [], ...fetched.warnings];
2811
+ }
2812
+ if (data.warnings && data.warnings.length > 0) {
2813
+ data.hints = [
2814
+ "peac.txt is a policy-document surface (docs/specs/PEAC-TXT.md). For key discovery, use /.well-known/peac-issuer.json (docs/specs/PEAC-ISSUER.md)."
2815
+ ];
2816
+ }
2817
+ return {
2818
+ success: true,
2819
+ data,
2820
+ timing: timer.end()
2821
+ };
2822
+ } catch (error) {
2823
+ return {
2824
+ ...handleError(error),
2825
+ timing: timer.end()
2826
+ };
2827
+ }
2828
+ }
2829
+ };
2596
2830
 
2597
2831
  // src/index.ts
2598
2832
  var program = new commander.Command();
@@ -2730,6 +2964,12 @@ program.addCommand(conformance);
2730
2964
  program.addCommand(samples);
2731
2965
  program.addCommand(reconcileCommand());
2732
2966
  doctor(program);
2967
+ program.command("discover").description("Discover and parse a remote /.well-known/peac.txt policy document").argument("<url>", "Origin URL (http/https) whose /.well-known/peac.txt to fetch").option("-j, --json", "output in JSON format").action(async (url, opts) => {
2968
+ const cmd = new DiscoverCommand();
2969
+ const result = await cmd.execute(url, { json: opts.json });
2970
+ console.log(formatOutput(result, opts.json));
2971
+ process.exitCode = result.success ? 0 : 1;
2972
+ });
2733
2973
  try {
2734
2974
  program.parse();
2735
2975
  } catch (err) {