@actiondock/core 2.6.0 → 2.7.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.
Files changed (53) hide show
  1. package/dist/errors.d.ts +20 -0
  2. package/dist/errors.js +20 -0
  3. package/dist/export/templates.js +42 -30
  4. package/dist/index.d.ts +3 -1
  5. package/dist/index.js +3 -1
  6. package/dist/input/advice.d.ts +100 -0
  7. package/dist/input/advice.js +767 -0
  8. package/dist/input/describe.d.ts +106 -0
  9. package/dist/input/describe.js +176 -0
  10. package/dist/input/file-input.d.ts +54 -0
  11. package/dist/input/file-input.js +124 -0
  12. package/dist/input/flat-decode.d.ts +11 -0
  13. package/dist/input/flat-decode.js +13 -0
  14. package/dist/input/flat-errors.d.ts +72 -0
  15. package/dist/input/flat-errors.js +87 -0
  16. package/dist/input/flat-materializer.d.ts +29 -0
  17. package/dist/input/flat-materializer.js +179 -0
  18. package/dist/input/flat-parser.d.ts +50 -0
  19. package/dist/input/flat-parser.js +169 -0
  20. package/dist/input/flat-predicates.d.ts +34 -0
  21. package/dist/input/flat-predicates.js +50 -0
  22. package/dist/input/index.d.ts +14 -0
  23. package/dist/input/index.js +14 -0
  24. package/dist/input/input-resolver.d.ts +63 -0
  25. package/dist/input/input-resolver.js +254 -0
  26. package/dist/input/json-depth-scanner.d.ts +17 -0
  27. package/dist/input/json-depth-scanner.js +49 -0
  28. package/dist/input/metadata.d.ts +99 -0
  29. package/dist/input/metadata.js +99 -0
  30. package/dist/input/stdin-input.d.ts +26 -0
  31. package/dist/input/stdin-input.js +125 -0
  32. package/dist/input/utf8.d.ts +19 -0
  33. package/dist/input/utf8.js +36 -0
  34. package/dist/input/validation-mapper.d.ts +27 -0
  35. package/dist/input/validation-mapper.js +144 -0
  36. package/dist/json/index.d.ts +1 -0
  37. package/dist/json/index.js +1 -0
  38. package/dist/json/value-validator.d.ts +88 -0
  39. package/dist/json/value-validator.js +330 -0
  40. package/dist/profile/client-health.js +1 -0
  41. package/dist/profile/client-transport.js +1 -0
  42. package/dist/project/init.js +2 -2
  43. package/dist/runtime/runner.d.ts +2 -12
  44. package/dist/runtime/runner.js +38 -68
  45. package/dist/runtime/standalone.d.ts +14 -1
  46. package/dist/runtime/standalone.js +177 -71
  47. package/dist/schema/index.d.ts +1 -0
  48. package/dist/schema/index.js +1 -0
  49. package/dist/schema/validator.d.ts +10 -0
  50. package/dist/schema/validator.js +48 -54
  51. package/dist/version.d.ts +1 -1
  52. package/dist/version.js +1 -1
  53. package/package.json +2 -2
@@ -0,0 +1,19 @@
1
+ import type { InputValidationSource } from "./flat-errors.js";
2
+ /**
3
+ * 校验并严格解码字节序列为合法 UTF-8 字符串。
4
+ * 使用 TextDecoder("utf-8", { fatal: true }) 拦截非法编码字节并抛出结构化 INVALID_JSON 异常。
5
+ *
6
+ * @param bytes 待解码的原始字节数据
7
+ * @param source 输入校验来源(默认为 full-json-inline)
8
+ * @returns 解码后的 UTF-8 文本
9
+ * @throws InputError 当包含非法 UTF-8 字节序列时抛出 INVALID_JSON 异常
10
+ */
11
+ export declare function decodeUtf8Strict(bytes: Uint8Array | Buffer, source?: InputValidationSource): string;
12
+ /**
13
+ * 仅剥离输入字符串起始处的恰好一个 U+FEFF BOM。
14
+ * 若存在双重 BOM(\uFEFF\uFEFF),仅剥离首个,第二个保留给后续 JSON 解析器以触发 SYNTAX_ERROR。
15
+ *
16
+ * @param text 原始输入文本
17
+ * @returns 剥离首个 BOM 后的文本
18
+ */
19
+ export declare function stripBom(text: string): string;
@@ -0,0 +1,36 @@
1
+ import { mapInputValidationFailure } from "./validation-mapper.js";
2
+ /**
3
+ * 校验并严格解码字节序列为合法 UTF-8 字符串。
4
+ * 使用 TextDecoder("utf-8", { fatal: true }) 拦截非法编码字节并抛出结构化 INVALID_JSON 异常。
5
+ *
6
+ * @param bytes 待解码的原始字节数据
7
+ * @param source 输入校验来源(默认为 full-json-inline)
8
+ * @returns 解码后的 UTF-8 文本
9
+ * @throws InputError 当包含非法 UTF-8 字节序列时抛出 INVALID_JSON 异常
10
+ */
11
+ export function decodeUtf8Strict(bytes, source = "full-json-inline") {
12
+ try {
13
+ const decoder = new TextDecoder("utf-8", { fatal: true });
14
+ return decoder.decode(bytes);
15
+ }
16
+ catch (err) {
17
+ throw mapInputValidationFailure(source, {
18
+ valid: false,
19
+ code: "INVALID_UTF8",
20
+ reason: `Invalid UTF-8 encoding: ${err instanceof Error ? err.message : String(err)}`,
21
+ });
22
+ }
23
+ }
24
+ /**
25
+ * 仅剥离输入字符串起始处的恰好一个 U+FEFF BOM。
26
+ * 若存在双重 BOM(\uFEFF\uFEFF),仅剥离首个,第二个保留给后续 JSON 解析器以触发 SYNTAX_ERROR。
27
+ *
28
+ * @param text 原始输入文本
29
+ * @returns 剥离首个 BOM 后的文本
30
+ */
31
+ export function stripBom(text) {
32
+ if (text.charCodeAt(0) === 0xfeff) {
33
+ return text.slice(1);
34
+ }
35
+ return text;
36
+ }
@@ -0,0 +1,27 @@
1
+ import { InputError, type InputValidationSource } from "./flat-errors.js";
2
+ import type { ActionInputValidationResult, JsonValueValidationResult } from "../json/value-validator.js";
3
+ /**
4
+ * 校验失败结构化输入描述。
5
+ */
6
+ export type ValidationFailureInput = Exclude<ActionInputValidationResult, {
7
+ valid: true;
8
+ }> | Exclude<JsonValueValidationResult, {
9
+ valid: true;
10
+ }> | {
11
+ valid: false;
12
+ kind?: "json-value" | "input-policy";
13
+ code: string;
14
+ reason: string;
15
+ path?: string;
16
+ property?: string;
17
+ [key: string]: unknown;
18
+ };
19
+ /**
20
+ * 将校验失败结果根据输入来源映射为统一结构化异常。
21
+ * 遵循技术设计文档第 19 节 Mapping Table 规范。
22
+ *
23
+ * @param source 输入校验来源
24
+ * @param result 校验失败结果对象
25
+ * @returns 映射后的结构化输入异常
26
+ */
27
+ export declare function mapInputValidationFailure(source: InputValidationSource, result: ActionInputValidationResult | JsonValueValidationResult | ValidationFailureInput): InputError;
@@ -0,0 +1,144 @@
1
+ import { INPUT_NOT_JSON, INPUT_VALIDATION_FAILED, } from "../errors.js";
2
+ import { InputError, invalidJson, invalidJsonLiteral, flatInputLimitExceeded, inputLimitExceeded, inputPolicyViolation, } from "./flat-errors.js";
3
+ /**
4
+ * 将校验失败结果根据输入来源映射为统一结构化异常。
5
+ * 遵循技术设计文档第 19 节 Mapping Table 规范。
6
+ *
7
+ * @param source 输入校验来源
8
+ * @param result 校验失败结果对象
9
+ * @returns 映射后的结构化输入异常
10
+ */
11
+ export function mapInputValidationFailure(source, result) {
12
+ const failure = result;
13
+ const code = failure.code;
14
+ const reason = failure.reason;
15
+ const path = failure.path;
16
+ const isPolicyViolation = failure.kind === "input-policy" || code === "FORBIDDEN_PROPERTY";
17
+ switch (source) {
18
+ case "flat-json-literal": {
19
+ if (code === "SYNTAX_ERROR") {
20
+ return invalidJsonLiteral(reason || "Invalid JSON literal syntax", {
21
+ reason: "SYNTAX_ERROR",
22
+ path,
23
+ });
24
+ }
25
+ if (code === "NON_FINITE_NUMBER") {
26
+ return invalidJsonLiteral(reason || "Non-finite number in JSON literal", {
27
+ reason: "NON_FINITE_NUMBER",
28
+ path,
29
+ });
30
+ }
31
+ if (code === "MAX_JSON_DEPTH") {
32
+ return invalidJsonLiteral(reason || "Max JSON depth limit exceeded in JSON literal", {
33
+ reason: "MAX_JSON_DEPTH",
34
+ path,
35
+ });
36
+ }
37
+ return invalidJsonLiteral(reason || "Invalid JSON literal value", {
38
+ reason: "INVALID_JSON_VALUE",
39
+ path,
40
+ });
41
+ }
42
+ case "full-json-inline":
43
+ case "full-json-file":
44
+ case "full-json-stdin": {
45
+ const cliSource = source === "full-json-inline"
46
+ ? "inline-json"
47
+ : source === "full-json-file"
48
+ ? "file"
49
+ : "stdin";
50
+ if (code === "SYNTAX_ERROR") {
51
+ return invalidJson(reason || "Invalid JSON syntax", {
52
+ reason: "SYNTAX_ERROR",
53
+ source: cliSource,
54
+ path,
55
+ });
56
+ }
57
+ if (code === "INVALID_UTF8") {
58
+ return invalidJson(reason || "Invalid UTF-8 encoding in JSON input", {
59
+ reason: "INVALID_UTF8",
60
+ source: cliSource,
61
+ path,
62
+ });
63
+ }
64
+ if (code === "NON_FINITE_NUMBER") {
65
+ return invalidJson(reason || "Number is non-finite or NaN", {
66
+ reason: "NON_FINITE_NUMBER",
67
+ source: cliSource,
68
+ path,
69
+ });
70
+ }
71
+ if (code === "MAX_JSON_DEPTH") {
72
+ return invalidJson(reason || "Max JSON depth limit exceeded", {
73
+ reason: "MAX_JSON_DEPTH",
74
+ source: cliSource,
75
+ path,
76
+ });
77
+ }
78
+ return invalidJson(reason || "Invalid JSON value", {
79
+ reason: "INVALID_JSON_VALUE",
80
+ source: cliSource,
81
+ path,
82
+ });
83
+ }
84
+ case "flat-materialized": {
85
+ if (code === "MAX_JSON_DEPTH") {
86
+ return flatInputLimitExceeded(reason || "Materialized JSON depth limit exceeded", {
87
+ reason: "MAX_MATERIALIZED_JSON_DEPTH",
88
+ path,
89
+ });
90
+ }
91
+ if (code === "MAX_MATERIALIZED_BYTES") {
92
+ return flatInputLimitExceeded(reason || "Materialized input size exceeds limit", {
93
+ reason: "MAX_MATERIALIZED_BYTES",
94
+ path,
95
+ });
96
+ }
97
+ return flatInputLimitExceeded(reason || "Materialized input validation failed", {
98
+ reason: "INVALID_JSON_VALUE",
99
+ path,
100
+ });
101
+ }
102
+ case "cli-pre-target": {
103
+ if (isPolicyViolation) {
104
+ const property = failure.property;
105
+ return inputPolicyViolation(reason || `Forbidden property "${property}" is not allowed in Action input`, {
106
+ reason: "FORBIDDEN_PROPERTY",
107
+ property,
108
+ path,
109
+ });
110
+ }
111
+ if (code === "MAX_JSON_DEPTH") {
112
+ return inputLimitExceeded(reason || "Max JSON depth limit exceeded", {
113
+ reason: "MAX_JSON_DEPTH",
114
+ path,
115
+ });
116
+ }
117
+ if (code === "NON_FINITE_NUMBER") {
118
+ return invalidJson(reason || "Number is non-finite or NaN", {
119
+ reason: "NON_FINITE_NUMBER",
120
+ path,
121
+ });
122
+ }
123
+ return invalidJson(reason || "Invalid JSON value in Action input", {
124
+ reason: "INVALID_JSON_VALUE",
125
+ path,
126
+ });
127
+ }
128
+ case "runtime": {
129
+ if (isPolicyViolation) {
130
+ return new InputError(INPUT_VALIDATION_FAILED, reason || "Action input contains forbidden property", [reason || "Forbidden property in input"]);
131
+ }
132
+ return new InputError(INPUT_NOT_JSON, reason || "Input is not a valid JSON value", {
133
+ reason: code,
134
+ path,
135
+ });
136
+ }
137
+ default: {
138
+ return new InputError(INPUT_NOT_JSON, reason || "Input validation failed", {
139
+ reason: code,
140
+ path,
141
+ });
142
+ }
143
+ }
144
+ }
@@ -0,0 +1 @@
1
+ export * from "./value-validator.js";
@@ -0,0 +1 @@
1
+ export * from "./value-validator.js";
@@ -0,0 +1,88 @@
1
+ import type { JsonValue } from "@actiondock/sdk";
2
+ /** 默认 JSON 最大嵌套深度限制(防止恶意超深结构) */
3
+ export declare const DEFAULT_MAX_JSON_DEPTH = 256;
4
+ /**
5
+ * JSON 结构校验选项。
6
+ */
7
+ export interface ValidateJsonOptions {
8
+ /** 最大允许嵌套深度(默认 256) */
9
+ maxDepth?: number;
10
+ }
11
+ /**
12
+ * Action 输入校验选项。
13
+ */
14
+ export interface ValidateActionInputOptions extends ValidateJsonOptions {
15
+ }
16
+ /**
17
+ * JSON 值校验错误码类型。
18
+ */
19
+ export type JsonValueValidationErrorCode = "NON_FINITE_NUMBER" | "MAX_JSON_DEPTH" | "CIRCULAR_REFERENCE" | "UNSUPPORTED_JSON_TYPE" | "INVALID_JSON_OBJECT" | "UNDEFINED_JSON_VALUE";
20
+ /**
21
+ * Canonical JsonValue 校验结果。
22
+ */
23
+ export type JsonValueValidationResult = {
24
+ valid: true;
25
+ } | {
26
+ valid: false;
27
+ code: JsonValueValidationErrorCode;
28
+ reason: string;
29
+ path?: string;
30
+ };
31
+ /**
32
+ * Action 输入值校验结果(包含策略违规分支)。
33
+ */
34
+ export type ActionInputValidationResult = {
35
+ valid: true;
36
+ } | {
37
+ valid: false;
38
+ kind: "json-value";
39
+ code: JsonValueValidationErrorCode;
40
+ reason: string;
41
+ path?: string;
42
+ } | {
43
+ valid: false;
44
+ kind: "input-policy";
45
+ code: "FORBIDDEN_PROPERTY";
46
+ property: string;
47
+ path: string;
48
+ reason: string;
49
+ };
50
+ /**
51
+ * 按照 RFC 6901 转义单个 JSON Pointer 路径段。
52
+ *
53
+ * @param segment 路径段(属性名或数组索引)
54
+ * @returns 转义后的路径段
55
+ */
56
+ export declare function escapeJsonPointerSegment(segment: string | number): string;
57
+ /**
58
+ * 将路径段追加到基础 RFC 6901 JSON Pointer 路径后。
59
+ *
60
+ * @param basePath 基础路径
61
+ * @param segment 待追加的路径段
62
+ * @returns 拼接后的 JSON Pointer
63
+ */
64
+ export declare function appendJsonPointer(basePath: string, segment: string | number): string;
65
+ /**
66
+ * 校验值是否为合法的 Canonical JSON 兼容结构。
67
+ *
68
+ * @param value 待校验的目标值
69
+ * @param options 校验配置选项
70
+ * @returns 校验结果对象
71
+ */
72
+ export declare function validateJsonValue(value: unknown, options?: ValidateJsonOptions): JsonValueValidationResult;
73
+ /**
74
+ * 校验 Action 输入值是否满足 Canonical JsonValue 规范且符合 ActionDock 安全策略。
75
+ * 在遍历过程中递归检查是否存在禁止属性(__proto__、constructor、prototype)。
76
+ *
77
+ * @param value 待校验的 Action 输入值
78
+ * @param options 校验配置选项
79
+ * @returns 校验结果对象
80
+ */
81
+ export declare function validateActionInputValue(value: unknown, options?: ValidateActionInputOptions): ActionInputValidationResult;
82
+ /**
83
+ * 断言值必须为合法的 Canonical JSON 兼容结构,若非法则抛出 TypeError。
84
+ *
85
+ * @param value 待断言的目标值
86
+ * @param options 校验配置选项
87
+ */
88
+ export declare function assertJsonValue(value: unknown, options?: ValidateJsonOptions): asserts value is JsonValue;
@@ -0,0 +1,330 @@
1
+ import { isForbiddenActionInputPropertyName } from "../input/flat-predicates.js";
2
+ /** 默认 JSON 最大嵌套深度限制(防止恶意超深结构) */
3
+ export const DEFAULT_MAX_JSON_DEPTH = 256;
4
+ /**
5
+ * 按照 RFC 6901 转义单个 JSON Pointer 路径段。
6
+ *
7
+ * @param segment 路径段(属性名或数组索引)
8
+ * @returns 转义后的路径段
9
+ */
10
+ export function escapeJsonPointerSegment(segment) {
11
+ return String(segment).replace(/~/g, "~0").replace(/\//g, "~1");
12
+ }
13
+ /**
14
+ * 将路径段追加到基础 RFC 6901 JSON Pointer 路径后。
15
+ *
16
+ * @param basePath 基础路径
17
+ * @param segment 待追加的路径段
18
+ * @returns 拼接后的 JSON Pointer
19
+ */
20
+ export function appendJsonPointer(basePath, segment) {
21
+ return `${basePath}/${escapeJsonPointerSegment(segment)}`;
22
+ }
23
+ /**
24
+ * 内部统一 JSON 值与输入策略遍历校验引擎。
25
+ *
26
+ * 遍历规范:
27
+ * - 迭代式显式栈遍历:模拟调用栈,杜绝深层结构造成的堆栈溢出。
28
+ * - 严格数值有限性:全链路检查所有数值满足 Number.isFinite,拦截 NaN 与 Infinity。
29
+ * - 活跃祖先集合环路检测:仅在当前子树遍历期间在祖先集合中保留,完美放行有向无环(DAG)共享子结构。
30
+ * - 严格对象与原型校验:仅允许 Object.prototype 与 null 原型,拒绝 Date、Map、Set 等非纯数据对象与类实例。
31
+ * - 严格数组紧凑性:校验 0..length-1 全部存在,拒绝稀疏数组、额外属性与访问器元素。
32
+ * - Proxy 异常防御:对所有反射与属性检查执行防护拦截,杜绝未分类异常泄漏。
33
+ * - RFC 6901 路径追踪:全链路维护转义标准 JSON Pointer 路径。
34
+ */
35
+ function walkJsonValue(value, options, checkForbiddenProperties = false) {
36
+ const maxDepth = options?.maxDepth ?? DEFAULT_MAX_JSON_DEPTH;
37
+ const ancestors = new Set();
38
+ const stack = [{ type: "ENTER", value, depth: 0, path: "" }];
39
+ while (stack.length > 0) {
40
+ const frame = stack.pop();
41
+ if (frame.type === "EXIT") {
42
+ ancestors.delete(frame.target);
43
+ continue;
44
+ }
45
+ const { value: val, depth, path } = frame;
46
+ if (val === null || typeof val === "boolean" || typeof val === "string") {
47
+ continue;
48
+ }
49
+ if (typeof val === "number") {
50
+ if (!Number.isFinite(val) || Number.isNaN(val)) {
51
+ return {
52
+ valid: false,
53
+ kind: "json-value",
54
+ code: "NON_FINITE_NUMBER",
55
+ reason: `Number is non-finite or NaN (${val})`,
56
+ path,
57
+ };
58
+ }
59
+ continue;
60
+ }
61
+ if (typeof val === "undefined") {
62
+ return {
63
+ valid: false,
64
+ kind: "json-value",
65
+ code: "UNDEFINED_JSON_VALUE",
66
+ reason: "Undefined value is not allowed in JSON",
67
+ path,
68
+ };
69
+ }
70
+ if (typeof val === "function" ||
71
+ typeof val === "symbol" ||
72
+ typeof val === "bigint") {
73
+ return {
74
+ valid: false,
75
+ kind: "json-value",
76
+ code: "UNSUPPORTED_JSON_TYPE",
77
+ reason: `Unsupported JSON type '${typeof val}'`,
78
+ path,
79
+ };
80
+ }
81
+ if (typeof val === "object") {
82
+ try {
83
+ if (ancestors.has(val)) {
84
+ return {
85
+ valid: false,
86
+ kind: "json-value",
87
+ code: "CIRCULAR_REFERENCE",
88
+ reason: "Circular reference detected in object structure",
89
+ path,
90
+ };
91
+ }
92
+ if (depth > maxDepth) {
93
+ return {
94
+ valid: false,
95
+ kind: "json-value",
96
+ code: "MAX_JSON_DEPTH",
97
+ reason: `Max JSON depth limit (${maxDepth}) exceeded`,
98
+ path,
99
+ };
100
+ }
101
+ const proto = Object.getPrototypeOf(val);
102
+ const isArr = Array.isArray(val);
103
+ if (isArr) {
104
+ if (proto !== Array.prototype) {
105
+ return {
106
+ valid: false,
107
+ kind: "json-value",
108
+ code: "INVALID_JSON_OBJECT",
109
+ reason: "Array prototype must be Array.prototype",
110
+ path,
111
+ };
112
+ }
113
+ const len = val.length;
114
+ if (typeof len !== "number" ||
115
+ !Number.isInteger(len) ||
116
+ len < 0 ||
117
+ len > Number.MAX_SAFE_INTEGER) {
118
+ return {
119
+ valid: false,
120
+ kind: "json-value",
121
+ code: "INVALID_JSON_OBJECT",
122
+ reason: "Array length is invalid",
123
+ path,
124
+ };
125
+ }
126
+ const ownKeys = Reflect.ownKeys(val);
127
+ if (ownKeys.length !== len + 1) {
128
+ return {
129
+ valid: false,
130
+ kind: "json-value",
131
+ code: "INVALID_JSON_OBJECT",
132
+ reason: "Array must be dense without extra properties or holes",
133
+ path,
134
+ };
135
+ }
136
+ ancestors.add(val);
137
+ stack.push({ type: "EXIT", target: val });
138
+ for (let i = len - 1; i >= 0; i--) {
139
+ const keyStr = String(i);
140
+ const desc = Object.getOwnPropertyDescriptor(val, keyStr);
141
+ const elemPath = appendJsonPointer(path, i);
142
+ if (!desc) {
143
+ return {
144
+ valid: false,
145
+ kind: "json-value",
146
+ code: "INVALID_JSON_OBJECT",
147
+ reason: `Sparse array (missing element at index ${i})`,
148
+ path: elemPath,
149
+ };
150
+ }
151
+ if (desc.get !== undefined || desc.set !== undefined) {
152
+ return {
153
+ valid: false,
154
+ kind: "json-value",
155
+ code: "INVALID_JSON_OBJECT",
156
+ reason: `Accessor property not allowed at array index ${i}`,
157
+ path: elemPath,
158
+ };
159
+ }
160
+ if (!desc.enumerable) {
161
+ return {
162
+ valid: false,
163
+ kind: "json-value",
164
+ code: "INVALID_JSON_OBJECT",
165
+ reason: `Non-enumerable property not allowed at array index ${i}`,
166
+ path: elemPath,
167
+ };
168
+ }
169
+ if (desc.value === undefined) {
170
+ return {
171
+ valid: false,
172
+ kind: "json-value",
173
+ code: "UNDEFINED_JSON_VALUE",
174
+ reason: `Undefined value is not allowed at array index ${i}`,
175
+ path: elemPath,
176
+ };
177
+ }
178
+ stack.push({
179
+ type: "ENTER",
180
+ value: desc.value,
181
+ depth: depth + 1,
182
+ path: elemPath,
183
+ });
184
+ }
185
+ }
186
+ else {
187
+ if (proto !== Object.prototype && proto !== null) {
188
+ return {
189
+ valid: false,
190
+ kind: "json-value",
191
+ code: "INVALID_JSON_OBJECT",
192
+ reason: "Object prototype must be Object.prototype or null",
193
+ path,
194
+ };
195
+ }
196
+ const ownKeys = Reflect.ownKeys(val);
197
+ ancestors.add(val);
198
+ stack.push({ type: "EXIT", target: val });
199
+ for (let i = ownKeys.length - 1; i >= 0; i--) {
200
+ const key = ownKeys[i];
201
+ if (typeof key !== "string") {
202
+ return {
203
+ valid: false,
204
+ kind: "json-value",
205
+ code: "INVALID_JSON_OBJECT",
206
+ reason: "Symbol-keyed properties are not allowed in JSON object",
207
+ path: appendJsonPointer(path, String(key)),
208
+ };
209
+ }
210
+ const propPath = appendJsonPointer(path, key);
211
+ if (checkForbiddenProperties &&
212
+ isForbiddenActionInputPropertyName(key)) {
213
+ return {
214
+ valid: false,
215
+ kind: "input-policy",
216
+ code: "FORBIDDEN_PROPERTY",
217
+ property: key,
218
+ path: propPath,
219
+ reason: `Forbidden property "${key}" is not allowed in Action input`,
220
+ };
221
+ }
222
+ const desc = Object.getOwnPropertyDescriptor(val, key);
223
+ if (!desc) {
224
+ return {
225
+ valid: false,
226
+ kind: "json-value",
227
+ code: "INVALID_JSON_OBJECT",
228
+ reason: `Property descriptor missing for key "${key}"`,
229
+ path: propPath,
230
+ };
231
+ }
232
+ if (desc.get !== undefined || desc.set !== undefined) {
233
+ return {
234
+ valid: false,
235
+ kind: "json-value",
236
+ code: "INVALID_JSON_OBJECT",
237
+ reason: `Accessor properties (getters/setters) are not allowed for key "${key}"`,
238
+ path: propPath,
239
+ };
240
+ }
241
+ if (!desc.enumerable) {
242
+ return {
243
+ valid: false,
244
+ kind: "json-value",
245
+ code: "INVALID_JSON_OBJECT",
246
+ reason: `Non-enumerable properties are not allowed for key "${key}"`,
247
+ path: propPath,
248
+ };
249
+ }
250
+ if (desc.value === undefined) {
251
+ return {
252
+ valid: false,
253
+ kind: "json-value",
254
+ code: "UNDEFINED_JSON_VALUE",
255
+ reason: `Undefined value is not allowed for key "${key}"`,
256
+ path: propPath,
257
+ };
258
+ }
259
+ stack.push({
260
+ type: "ENTER",
261
+ value: desc.value,
262
+ depth: depth + 1,
263
+ path: propPath,
264
+ });
265
+ }
266
+ }
267
+ }
268
+ catch (err) {
269
+ return {
270
+ valid: false,
271
+ kind: "json-value",
272
+ code: "INVALID_JSON_OBJECT",
273
+ reason: `Failed to inspect object: ${err instanceof Error ? err.message : String(err)}`,
274
+ path,
275
+ };
276
+ }
277
+ }
278
+ }
279
+ return { valid: true };
280
+ }
281
+ /**
282
+ * 校验值是否为合法的 Canonical JSON 兼容结构。
283
+ *
284
+ * @param value 待校验的目标值
285
+ * @param options 校验配置选项
286
+ * @returns 校验结果对象
287
+ */
288
+ export function validateJsonValue(value, options) {
289
+ const res = walkJsonValue(value, options, false);
290
+ if (res.valid) {
291
+ return { valid: true };
292
+ }
293
+ if (res.kind === "input-policy") {
294
+ return {
295
+ valid: false,
296
+ code: "INVALID_JSON_OBJECT",
297
+ reason: res.reason,
298
+ path: res.path,
299
+ };
300
+ }
301
+ return {
302
+ valid: false,
303
+ code: res.code,
304
+ reason: res.reason,
305
+ path: res.path,
306
+ };
307
+ }
308
+ /**
309
+ * 校验 Action 输入值是否满足 Canonical JsonValue 规范且符合 ActionDock 安全策略。
310
+ * 在遍历过程中递归检查是否存在禁止属性(__proto__、constructor、prototype)。
311
+ *
312
+ * @param value 待校验的 Action 输入值
313
+ * @param options 校验配置选项
314
+ * @returns 校验结果对象
315
+ */
316
+ export function validateActionInputValue(value, options) {
317
+ return walkJsonValue(value, options, true);
318
+ }
319
+ /**
320
+ * 断言值必须为合法的 Canonical JSON 兼容结构,若非法则抛出 TypeError。
321
+ *
322
+ * @param value 待断言的目标值
323
+ * @param options 校验配置选项
324
+ */
325
+ export function assertJsonValue(value, options) {
326
+ const result = validateJsonValue(value, options);
327
+ if (!result.valid) {
328
+ throw new TypeError(`Invalid JSON value: ${result.reason}`);
329
+ }
330
+ }
@@ -32,6 +32,7 @@ export async function checkRemoteHealth(serverUrl, token, timeoutMs = 5000, opti
32
32
  }
33
33
  else if (options?.insecure) {
34
34
  fetchInit.dispatcher = getInsecureDispatcher();
35
+ fetchInit.tls = { rejectUnauthorized: false };
35
36
  }
36
37
  const res = await fetchWithProtocolFallback(normalizeServerUrl(serverUrl), "/api/v2/health", fetchInit);
37
38
  const latencyMs = Date.now() - startTime;
@@ -101,6 +101,7 @@ export function createRemoteFetch(serverUrl, token, options) {
101
101
  }
102
102
  else if (options?.insecure) {
103
103
  fetchInit.dispatcher = getInsecureDispatcher();
104
+ fetchInit.tls = { rejectUnauthorized: false };
104
105
  }
105
106
  return fetchWithProtocolFallback(base, path, fetchInit);
106
107
  };
@@ -86,10 +86,10 @@ export function initProject(targetDir, options = {}) {
86
86
  node: ">=24.12.0",
87
87
  },
88
88
  dependencies: {
89
- "@actiondock/sdk": "^2.6.0",
89
+ "@actiondock/sdk": "^2.7.0",
90
90
  },
91
91
  devDependencies: {
92
- "@actiondock/testing": "^2.6.0",
92
+ "@actiondock/testing": "^2.7.0",
93
93
  "@types/node": "^22.13.0",
94
94
  "tsx": "^4.19.0",
95
95
  "typescript": "^5.7.0",