@skaleagents/swarm 0.6.1 → 0.6.3

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.
@@ -1,4 +1,4 @@
1
- export type IacFormat = "terraform" | "cloudformation" | "kubernetes";
1
+ export type IacFormat = "terraform" | "cloudformation" | "kubernetes" | "pulumi";
2
2
  export type Path = (string | number)[];
3
3
  export type Location = {
4
4
  line: number;
@@ -22,4 +22,5 @@ export declare class ScanInputError extends Error {
22
22
  export declare function object(value: unknown): Record<string, unknown>;
23
23
  export declare function array(value: unknown): unknown[];
24
24
  export declare function looksLikeIac(content: string): boolean;
25
+ export declare function looksLikePulumiYaml(content: string): boolean;
25
26
  export declare function parseIac(content: string, requested?: IacFormat | "auto"): ParsedIac;
package/dist/iac/parse.js CHANGED
@@ -37,11 +37,20 @@ export function looksLikeIac(content) {
37
37
  }
38
38
  return (/^\s*(?:resource|module|terraform|variable|provider)\s+["{]/m.test(content) ||
39
39
  /^\s*(?:["']?Resources["']?\s*:|apiVersion\s*:)/m.test(content) ||
40
- /^\s*\{\s*"(?:resource|Resources|apiVersion|AWSTemplateFormatVersion)"\s*:/.test(content));
40
+ /^\s*\{\s*"(?:resource|Resources|apiVersion|AWSTemplateFormatVersion)"\s*:/.test(content) ||
41
+ looksLikePulumiYaml(content));
42
+ }
43
+ export function looksLikePulumiYaml(content) {
44
+ return (/^\s*resources\s*:/m.test(content) &&
45
+ /^\s+type\s*:\s*[A-Za-z0-9_.-]+:\S+/m.test(content));
41
46
  }
42
47
  export function parseIac(content, requested = "auto") {
43
48
  if (!content.trim())
44
49
  throw new ScanInputError("Content must not be empty or whitespace.");
50
+ if (requested !== "terraform" &&
51
+ /(?:^|\n)\s*(?:import\s+\*\s+as\s+pulumi\b|import\s+pulumi\b|from\s+["']@pulumi\/)/.test(content)) {
52
+ throw new ScanInputError("Pulumi TypeScript, Python, and Go programs are not parsed yet. Submit Pulumi YAML.");
53
+ }
45
54
  const warnings = [];
46
55
  const resources = [];
47
56
  const isHcl = (requested === "terraform" && !content.trimStart().startsWith("{")) ||
@@ -142,13 +151,22 @@ export function parseIac(content, requested = "auto") {
142
151
  continue;
143
152
  checkShape(data);
144
153
  const root = object(data);
145
- const detected = root.Resources
146
- ? "cloudformation"
147
- : root.apiVersion && root.kind
148
- ? "kubernetes"
149
- : root.resource
150
- ? "terraform"
151
- : undefined;
154
+ const pulumiResources = pulumiResourceMap(root);
155
+ let detected;
156
+ if (requested === "pulumi" || (requested === "auto" && pulumiResources)) {
157
+ if (!pulumiResources)
158
+ throw new ScanInputError("Pulumi YAML requires a resources mapping. Each resource needs a type token. Pulumi TypeScript, Python, and Go programs are not parsed yet.");
159
+ detected = "pulumi";
160
+ }
161
+ else {
162
+ detected = root.Resources
163
+ ? "cloudformation"
164
+ : root.apiVersion && root.kind
165
+ ? "kubernetes"
166
+ : root.resource
167
+ ? "terraform"
168
+ : undefined;
169
+ }
152
170
  format ??= detected;
153
171
  if (!format || (detected && detected !== format))
154
172
  throw new ScanInputError("Input format is unsupported or mixed. Submit one IaC format per scan.");
@@ -170,7 +188,13 @@ export function parseIac(content, requested = "auto") {
170
188
  },
171
189
  });
172
190
  };
173
- if (format === "cloudformation") {
191
+ if (format === "pulumi") {
192
+ for (const [name, raw] of Object.entries(pulumiResources ?? {})) {
193
+ const value = object(raw);
194
+ add(name, String(value.type), value, ["resources", name]);
195
+ }
196
+ }
197
+ else if (format === "cloudformation") {
174
198
  if (!root.Resources ||
175
199
  Array.isArray(root.Resources) ||
176
200
  typeof root.Resources !== "object")
@@ -227,13 +251,24 @@ export function parseIac(content, requested = "auto") {
227
251
  }
228
252
  }
229
253
  if (!format)
230
- throw new ScanInputError("No IaC document found. Choose Terraform, CloudFormation, or Kubernetes.");
254
+ throw new ScanInputError("No IaC document found. Choose Terraform, CloudFormation, Kubernetes, or Pulumi YAML.");
231
255
  if (!resources.length)
232
256
  warnings.push("No resources found in the submitted document.");
233
257
  if (/(?:!(?:Ref|Sub|GetAtt|If)\b|"(?:Ref|Fn::\w+)"\s*:|\$\{)/.test(content))
234
258
  warnings.push("Intrinsic functions and expressions are not evaluated. Only literal configuration is checked.");
235
259
  return { format, resources, warnings };
236
260
  }
261
+ function pulumiResourceMap(root) {
262
+ const resources = object(root.resources);
263
+ const entries = Object.entries(resources);
264
+ if (entries.length > 0 &&
265
+ entries.every(([, raw]) => {
266
+ const type = object(raw).type;
267
+ return typeof type === "string" && type.includes(":");
268
+ }))
269
+ return resources;
270
+ return undefined;
271
+ }
237
272
  function escapeRegex(value) {
238
273
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
239
274
  }
package/dist/iac/rules.js CHANGED
@@ -242,15 +242,23 @@ function walk(value, visit, path = []) {
242
242
  }
243
243
  function checkCloud(resource, resources, format, check) {
244
244
  const tf = format === "terraform";
245
- const value = tf ? resource.value : object(resource.value.Properties);
246
- const prefix = tf ? [] : ["Properties"];
247
- const property = (hcl, cfn) => value[tf ? hcl : cfn];
248
- const path = (hcl, cfn) => [...prefix, tf ? hcl : cfn];
245
+ const pulumi = format === "pulumi";
246
+ const value = tf
247
+ ? resource.value
248
+ : pulumi
249
+ ? object(resource.value.properties)
250
+ : object(resource.value.Properties);
251
+ const prefix = tf ? [] : pulumi ? ["properties"] : ["Properties"];
252
+ const property = (hcl, cfn) => value[tf || pulumi ? hcl : cfn];
253
+ const path = (hcl, cfn) => [
254
+ ...prefix,
255
+ tf || pulumi ? hcl : cfn,
256
+ ];
249
257
  walk(value, (key, child, at, parent) => {
250
258
  const isIngress = !at.some((part) => /^(?:egress|SecurityGroupEgress)$/i.test(String(part))) &&
251
259
  !/egress/i.test(resource.type) &&
252
260
  value.type !== "egress";
253
- if (/^(?:cidr_blocks|ipv6_cidr_blocks|cidr_ipv4|cidr_ipv6|CidrIp|CidrIpv6)$/.test(key) &&
261
+ if (/^(?:cidr_blocks|ipv6_cidr_blocks|cidr_ipv4|cidr_ipv6|cidrBlocks|ipv6CidrBlocks|cidrBlock|CidrIp|CidrIpv6)$/.test(key) &&
254
262
  isIngress) {
255
263
  check("NET001", array(child).some((cidr) => cidr === "0.0.0.0/0" || cidr === "::/0"), [...prefix, ...at]);
256
264
  }
package/dist/server.js CHANGED
@@ -71,12 +71,13 @@ export function createServer(remote = false) {
71
71
  "terraform",
72
72
  "cloudformation",
73
73
  "kubernetes",
74
+ "pulumi",
74
75
  "application",
75
76
  "auto",
76
77
  ])
77
78
  .optional()
78
79
  .default("auto")
79
- .describe("Content format: terraform, cloudformation, kubernetes, application, auto"),
80
+ .describe("Content format: terraform, cloudformation, kubernetes, pulumi, application, auto"),
80
81
  },
81
82
  }, async ({ content, focus, format, minSeverity, maxFindings }) => {
82
83
  const botHints = fetchPublicBotHints();
@@ -123,7 +124,7 @@ export function createServer(remote = false) {
123
124
  description: (name === "scan_iac_stub"
124
125
  ? "Compatibility alias for scan_iac; runs the full scanner. "
125
126
  : "") +
126
- "Parse Terraform HCL/JSON, CloudFormation YAML/JSON, or Kubernetes manifests. Check security, reliability and cost rules with resource locations and remediation.",
127
+ "Parse Terraform HCL/JSON, CloudFormation YAML/JSON, Kubernetes manifests, or Pulumi YAML. Check security, reliability and cost rules with resource locations and remediation. Pulumi TypeScript, Python, and Go programs are not parsed yet.",
127
128
  annotations: {
128
129
  readOnlyHint: true,
129
130
  destructiveHint: false,
@@ -131,9 +132,9 @@ export function createServer(remote = false) {
131
132
  },
132
133
  _meta: { securitySchemes: [{ type: "oauth2", scopes: ["mcp"] }] },
133
134
  inputSchema: {
134
- content: contentSchema.describe("Terraform HCL/JSON, CloudFormation YAML/JSON, or Kubernetes YAML/JSON"),
135
+ content: contentSchema.describe("Terraform HCL/JSON, CloudFormation YAML/JSON, Kubernetes YAML/JSON, or Pulumi YAML"),
135
136
  format: z
136
- .enum(["terraform", "cloudformation", "kubernetes", "auto"])
137
+ .enum(["terraform", "cloudformation", "kubernetes", "pulumi", "auto"])
137
138
  .default("auto"),
138
139
  ...filters,
139
140
  },
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.6.1";
1
+ export declare const VERSION = "0.6.3";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.6.1";
1
+ export const VERSION = "0.6.3";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skaleagents/swarm",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"