@peac/cli 0.12.14 → 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 +1 -1
- package/dist/commands/discover.d.ts +7 -4
- package/dist/commands/discover.d.ts.map +1 -1
- package/dist/index.cjs +243 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +241 -4
- package/dist/index.mjs.map +1 -1
- package/dist/lib/conformance/validators.d.ts.map +1 -1
- package/dist/lib/conformance-runner.d.ts +2 -1
- package/dist/lib/conformance-runner.d.ts.map +1 -1
- package/dist/lib/policy-document-discovery.d.ts +68 -0
- package/dist/lib/policy-document-discovery.d.ts.map +1 -0
- package/package.json +8 -7
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
|
|
6
|
-
*
|
|
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,
|
|
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
|
|
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
|
|
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/
|
|
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 =
|
|
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) {
|