@skaleagents/swarm 0.6.12 → 0.6.13
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/iac/parse.d.ts +3 -1
- package/dist/iac/parse.js +264 -4
- package/dist/iac/rules.js +35 -1
- package/dist/server.js +7 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/iac/parse.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type IacFormat = "terraform" | "cloudformation" | "kubernetes" | "pulumi" | "compose" | "dockerfile" | "github" | "helm" | "ansible" | "bicep" | "arm" | "gitlab" | "azure-pipelines" | "cloudbuild" | "tfvars";
|
|
1
|
+
export type IacFormat = "terraform" | "cloudformation" | "kubernetes" | "pulumi" | "compose" | "dockerfile" | "github" | "helm" | "ansible" | "bicep" | "arm" | "gitlab" | "azure-pipelines" | "cloudbuild" | "tfvars" | "serverless" | "iam";
|
|
2
2
|
export type Path = (string | number)[];
|
|
3
3
|
export type Location = {
|
|
4
4
|
line: number;
|
|
@@ -33,3 +33,5 @@ export declare function looksLikeBicep(content: string): boolean;
|
|
|
33
33
|
export declare function looksLikeArm(content: string): boolean;
|
|
34
34
|
export declare function looksLikePulumiYaml(content: string): boolean;
|
|
35
35
|
export declare function parseIac(content: string, requested?: IacFormat | "auto"): ParsedIac;
|
|
36
|
+
export declare function looksLikeServerless(content: string): boolean;
|
|
37
|
+
export declare function looksLikeIamPolicy(content: string): boolean;
|
package/dist/iac/parse.js
CHANGED
|
@@ -30,6 +30,8 @@ export function looksLikeIac(content) {
|
|
|
30
30
|
const value = object(JSON.parse(content));
|
|
31
31
|
if (value.resource || value.Resources || (value.apiVersion && value.kind))
|
|
32
32
|
return true;
|
|
33
|
+
if (looksLikeIamPolicy(content))
|
|
34
|
+
return true;
|
|
33
35
|
}
|
|
34
36
|
catch {
|
|
35
37
|
/* Other format detection still applies to incomplete input. */
|
|
@@ -48,7 +50,9 @@ export function looksLikeIac(content) {
|
|
|
48
50
|
looksLikeBicep(content) ||
|
|
49
51
|
looksLikeArm(content) ||
|
|
50
52
|
looksLikePipeline(content) !== undefined ||
|
|
51
|
-
looksLikeTfvars(content)
|
|
53
|
+
looksLikeTfvars(content) ||
|
|
54
|
+
looksLikeServerless(content) ||
|
|
55
|
+
looksLikeIamPolicy(content));
|
|
52
56
|
}
|
|
53
57
|
export function looksLikePulumiProgram(content) {
|
|
54
58
|
return /(?:^|\n)\s*(?:import\s+\*\s+as\s+pulumi\b|import\s+pulumi\b|from\s+["']@pulumi\/)/.test(content);
|
|
@@ -135,6 +139,7 @@ export function looksLikePulumiYaml(content) {
|
|
|
135
139
|
return (/^\s*resources\s*:/m.test(content) &&
|
|
136
140
|
/^\s+type\s*:\s*[A-Za-z0-9_.-]+:\S+/m.test(content));
|
|
137
141
|
}
|
|
142
|
+
const terraformExpressionWarning = "Terraform expressions are not evaluated. Findings use literal values and declared settings.";
|
|
138
143
|
export function parseIac(content, requested = "auto") {
|
|
139
144
|
if (!content.trim())
|
|
140
145
|
throw new ScanInputError("Content must not be empty or whitespace.");
|
|
@@ -156,6 +161,14 @@ export function parseIac(content, requested = "auto") {
|
|
|
156
161
|
(requested === "auto" && looksLikeHelm(content))) {
|
|
157
162
|
return parseHelm(content);
|
|
158
163
|
}
|
|
164
|
+
if (requested === "serverless" ||
|
|
165
|
+
(requested === "auto" && looksLikeServerless(content))) {
|
|
166
|
+
return parseServerless(content);
|
|
167
|
+
}
|
|
168
|
+
if (requested === "iam" ||
|
|
169
|
+
(requested === "auto" && looksLikeIamPolicy(content))) {
|
|
170
|
+
return parseIamPolicy(content);
|
|
171
|
+
}
|
|
159
172
|
const pipeline = requested === "gitlab" ||
|
|
160
173
|
requested === "azure-pipelines" ||
|
|
161
174
|
requested === "cloudbuild"
|
|
@@ -257,8 +270,29 @@ export function parseIac(content, requested = "auto") {
|
|
|
257
270
|
}
|
|
258
271
|
if (object(data).module)
|
|
259
272
|
warnings.push("External modules are not expanded. The module body was not loaded, and the module source is not fetched or evaluated.");
|
|
273
|
+
const localBlocks = terraformLocalBlocks(data);
|
|
274
|
+
const localDeclarations = [
|
|
275
|
+
...searchable.matchAll(/\blocals\s*(?:"[^"]+"\s*)?\{/g),
|
|
276
|
+
];
|
|
277
|
+
localBlocks.forEach((block, index) => {
|
|
278
|
+
const offset = localDeclarations[index]?.index ?? 0;
|
|
279
|
+
const before = content.slice(0, offset);
|
|
280
|
+
resources.push({
|
|
281
|
+
id: `locals.${index + 1}`,
|
|
282
|
+
type: "terraform:local",
|
|
283
|
+
value: block,
|
|
284
|
+
locate: (path = []) => ({
|
|
285
|
+
line: before.split("\n").length,
|
|
286
|
+
column: offset - before.lastIndexOf("\n"),
|
|
287
|
+
path: ["locals", index, ...path].join("."),
|
|
288
|
+
}),
|
|
289
|
+
});
|
|
290
|
+
});
|
|
260
291
|
if (JSON.stringify(data).includes("${"))
|
|
261
|
-
warnings.push(
|
|
292
|
+
warnings.push(terraformExpressionWarning);
|
|
293
|
+
if (localBlocks.length > 0 &&
|
|
294
|
+
!warnings.some((warning) => warning.includes("expressions are not evaluated")))
|
|
295
|
+
warnings.push(terraformExpressionWarning);
|
|
262
296
|
if (!resources.some((item) => item.type !== "terraform:module" && item.type !== "terraform:variable"))
|
|
263
297
|
warnings.push("No resource declarations found. Data sources and outputs are not scanned as resources.");
|
|
264
298
|
return { format: "terraform", resources, warnings };
|
|
@@ -430,7 +464,7 @@ export function parseIac(content, requested = "auto") {
|
|
|
430
464
|
}
|
|
431
465
|
}
|
|
432
466
|
if (!format)
|
|
433
|
-
throw new ScanInputError("No IaC document found. Choose Terraform, CloudFormation, Kubernetes, Pulumi, Docker Compose, a Dockerfile, a GitHub Actions workflow, a Helm template, an Ansible playbook, Bicep,
|
|
467
|
+
throw new ScanInputError("No IaC document found. Choose Terraform, CloudFormation, Kubernetes, Pulumi, Docker Compose, a Dockerfile, a GitHub Actions workflow, a Helm template, an Ansible playbook, Bicep, an ARM template, Serverless Framework, or a standalone IAM policy.");
|
|
434
468
|
if (!resources.length)
|
|
435
469
|
warnings.push("No resources found in the submitted document.");
|
|
436
470
|
if (/(?:!(?:Ref|Sub|GetAtt|If)\b|"(?:Ref|Fn::\w+)"\s*:|\$\{)/.test(content))
|
|
@@ -693,7 +727,7 @@ function cloudBuildShape(content) {
|
|
|
693
727
|
return /^\s*-\s*name\s*:/m.test(content);
|
|
694
728
|
}
|
|
695
729
|
function looksLikePipeline(content) {
|
|
696
|
-
if (stolenPipelineInput(content))
|
|
730
|
+
if (stolenPipelineInput(content) || looksLikeServerless(content))
|
|
697
731
|
return undefined;
|
|
698
732
|
const visible = maskIaCComments(content);
|
|
699
733
|
if (azurePipelineShape(visible))
|
|
@@ -1369,3 +1403,229 @@ function parseAzure(content, format) {
|
|
|
1369
1403
|
warnings: [azureExpressionWarning],
|
|
1370
1404
|
};
|
|
1371
1405
|
}
|
|
1406
|
+
function terraformLocalBlocks(data) {
|
|
1407
|
+
const raw = object(data).locals;
|
|
1408
|
+
if (Array.isArray(raw))
|
|
1409
|
+
return raw.map((block) => object(block));
|
|
1410
|
+
if (raw && typeof raw === "object") {
|
|
1411
|
+
return Object.values(object(raw)).flatMap((blocks) => array(blocks).map((block) => object(block)));
|
|
1412
|
+
}
|
|
1413
|
+
return [];
|
|
1414
|
+
}
|
|
1415
|
+
export function looksLikeServerless(content) {
|
|
1416
|
+
if (looksLikeGithubWorkflow(content) || looksLikeCompose(content))
|
|
1417
|
+
return false;
|
|
1418
|
+
if (topLevelKey(content, "apiVersion") && topLevelKey(content, "kind"))
|
|
1419
|
+
return false;
|
|
1420
|
+
if (!topLevelKey(content, "service") || !topLevelKey(content, "provider"))
|
|
1421
|
+
return false;
|
|
1422
|
+
return topLevelKey(content, "functions") || topLevelKey(content, "plugins");
|
|
1423
|
+
}
|
|
1424
|
+
export function looksLikeIamPolicy(content) {
|
|
1425
|
+
const trimmed = content.trim();
|
|
1426
|
+
if (!trimmed.startsWith("{"))
|
|
1427
|
+
return false;
|
|
1428
|
+
let value;
|
|
1429
|
+
try {
|
|
1430
|
+
value = JSON.parse(trimmed);
|
|
1431
|
+
}
|
|
1432
|
+
catch {
|
|
1433
|
+
return false;
|
|
1434
|
+
}
|
|
1435
|
+
const root = object(value);
|
|
1436
|
+
if (typeof root.AWSTemplateFormatVersion === "string")
|
|
1437
|
+
return false;
|
|
1438
|
+
if ("resource" in root)
|
|
1439
|
+
return false;
|
|
1440
|
+
const version = root.Version ?? root.version;
|
|
1441
|
+
if (typeof version !== "string" || !version.includes("2012-10-17"))
|
|
1442
|
+
return false;
|
|
1443
|
+
const statement = root.Statement ?? root.statement;
|
|
1444
|
+
return Array.isArray(statement);
|
|
1445
|
+
}
|
|
1446
|
+
const serverlessWarning = "The Serverless Framework config is not executed. Conditions are not evaluated.";
|
|
1447
|
+
const iamPolicyWarning = "The IAM policy is not executed. Conditions are not evaluated.";
|
|
1448
|
+
function serverlessShapeError() {
|
|
1449
|
+
return "Serverless Framework config requires top-level service and provider, plus functions or plugins.";
|
|
1450
|
+
}
|
|
1451
|
+
function iamShapeError() {
|
|
1452
|
+
return "An IAM policy requires Version 2012-10-17 and a Statement array.";
|
|
1453
|
+
}
|
|
1454
|
+
function effectAllows(body) {
|
|
1455
|
+
const effect = body.Effect ?? body.effect;
|
|
1456
|
+
if (effect == null || String(effect).trim() === "")
|
|
1457
|
+
return true;
|
|
1458
|
+
return String(effect).trim().toLowerCase() === "allow";
|
|
1459
|
+
}
|
|
1460
|
+
function actionIsOnlyWildcard(body) {
|
|
1461
|
+
const action = body.Action ?? body.action ?? body.actions ?? body.Actions;
|
|
1462
|
+
if (typeof action === "string")
|
|
1463
|
+
return action.trim() === "*";
|
|
1464
|
+
return (Array.isArray(action) &&
|
|
1465
|
+
action.length > 0 &&
|
|
1466
|
+
action.every((item) => typeof item === "string" && item.trim() === "*"));
|
|
1467
|
+
}
|
|
1468
|
+
function serverlessReference(value) {
|
|
1469
|
+
return /^\$\{[\s\S]+\}$/.test(value.trim());
|
|
1470
|
+
}
|
|
1471
|
+
function yamlLocation(doc, lines, path) {
|
|
1472
|
+
const node = doc.getIn(path, true);
|
|
1473
|
+
const position = lines.linePos(node?.range?.[0] ?? doc.range?.[0] ?? 0);
|
|
1474
|
+
return {
|
|
1475
|
+
line: position.line,
|
|
1476
|
+
column: position.col,
|
|
1477
|
+
path: path.join("."),
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
function statementGroups(root) {
|
|
1481
|
+
const groups = [];
|
|
1482
|
+
const seen = new Set();
|
|
1483
|
+
const add = (statements, path) => {
|
|
1484
|
+
if (!Array.isArray(statements) || seen.has(statements))
|
|
1485
|
+
return;
|
|
1486
|
+
seen.add(statements);
|
|
1487
|
+
groups.push({ path, statements });
|
|
1488
|
+
};
|
|
1489
|
+
const provider = object(root.provider);
|
|
1490
|
+
add(provider.iamRoleStatements, ["provider", "iamRoleStatements"]);
|
|
1491
|
+
add(root.iamRoleStatements, ["iamRoleStatements"]);
|
|
1492
|
+
add(object(object(provider.iam).role).statements, ["provider", "iam", "role", "statements"]);
|
|
1493
|
+
add(object(object(root.iam).role).statements, ["iam", "role", "statements"]);
|
|
1494
|
+
if (Array.isArray(provider.iam))
|
|
1495
|
+
add(provider.iam, ["provider", "iam"]);
|
|
1496
|
+
else
|
|
1497
|
+
add(object(provider.iam).statements, ["provider", "iam", "statements"]);
|
|
1498
|
+
return groups;
|
|
1499
|
+
}
|
|
1500
|
+
function parseServerless(content) {
|
|
1501
|
+
if (!looksLikeServerless(content))
|
|
1502
|
+
throw new ScanInputError(serverlessShapeError());
|
|
1503
|
+
const lines = new LineCounter();
|
|
1504
|
+
let documents;
|
|
1505
|
+
try {
|
|
1506
|
+
documents = parseAllDocuments(content, {
|
|
1507
|
+
lineCounter: lines,
|
|
1508
|
+
prettyErrors: false,
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
catch {
|
|
1512
|
+
throw new ScanInputError(serverlessShapeError());
|
|
1513
|
+
}
|
|
1514
|
+
const doc = documents.find((item) => item.toJS() != null);
|
|
1515
|
+
if (!doc || doc.errors.length)
|
|
1516
|
+
throw new ScanInputError(serverlessShapeError());
|
|
1517
|
+
let data;
|
|
1518
|
+
try {
|
|
1519
|
+
data = doc.toJS({ maxAliasCount: 0 });
|
|
1520
|
+
}
|
|
1521
|
+
catch {
|
|
1522
|
+
throw new ScanInputError("YAML aliases are not supported. Expand anchors before scanning.");
|
|
1523
|
+
}
|
|
1524
|
+
const root = object(data);
|
|
1525
|
+
if (root.service == null ||
|
|
1526
|
+
root.provider == null ||
|
|
1527
|
+
(root.functions == null && root.plugins == null))
|
|
1528
|
+
throw new ScanInputError(serverlessShapeError());
|
|
1529
|
+
const resources = [];
|
|
1530
|
+
let iamCount = 0;
|
|
1531
|
+
for (const group of statementGroups(root)) {
|
|
1532
|
+
group.statements.forEach((statement, index) => {
|
|
1533
|
+
const body = object(statement);
|
|
1534
|
+
if (!effectAllows(body) || !actionIsOnlyWildcard(body))
|
|
1535
|
+
return;
|
|
1536
|
+
iamCount += 1;
|
|
1537
|
+
const path = [...group.path, index, "Action"];
|
|
1538
|
+
resources.push({
|
|
1539
|
+
id: `serverless.iam.${iamCount}`,
|
|
1540
|
+
type: "serverless:iam",
|
|
1541
|
+
value: { wildcard: true },
|
|
1542
|
+
locate: () => yamlLocation(doc, lines, path),
|
|
1543
|
+
});
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
if (iamCount === 0) {
|
|
1547
|
+
resources.push({
|
|
1548
|
+
id: "serverless.iam",
|
|
1549
|
+
type: "serverless:iam",
|
|
1550
|
+
value: { wildcard: false },
|
|
1551
|
+
locate: () => yamlLocation(doc, lines, ["provider", "iamRoleStatements"]),
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
const envHits = [];
|
|
1555
|
+
for (const [key, value] of Object.entries(object(object(root.provider).environment))) {
|
|
1556
|
+
if (secretVariableName(key))
|
|
1557
|
+
envHits.push({ path: ["provider", "environment", key], key, value });
|
|
1558
|
+
}
|
|
1559
|
+
for (const [name, raw] of Object.entries(object(root.functions))) {
|
|
1560
|
+
for (const [key, value] of Object.entries(object(object(raw).environment))) {
|
|
1561
|
+
if (secretVariableName(key))
|
|
1562
|
+
envHits.push({ path: ["functions", name, "environment", key], key, value });
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
if (envHits.length === 0) {
|
|
1566
|
+
resources.push({
|
|
1567
|
+
id: "serverless.environment",
|
|
1568
|
+
type: "serverless:env",
|
|
1569
|
+
value: { literal: false },
|
|
1570
|
+
locate: () => yamlLocation(doc, lines, ["provider", "environment"]),
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
else {
|
|
1574
|
+
envHits.forEach((hit, index) => {
|
|
1575
|
+
const literal = typeof hit.value === "string" &&
|
|
1576
|
+
hit.value.trim() !== "" &&
|
|
1577
|
+
!serverlessReference(hit.value);
|
|
1578
|
+
resources.push({
|
|
1579
|
+
id: `serverless.env.${index + 1}`,
|
|
1580
|
+
type: "serverless:env",
|
|
1581
|
+
value: { literal },
|
|
1582
|
+
locate: () => yamlLocation(doc, lines, hit.path),
|
|
1583
|
+
});
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
return { format: "serverless", resources, warnings: [serverlessWarning] };
|
|
1587
|
+
}
|
|
1588
|
+
function parseIamPolicy(content) {
|
|
1589
|
+
if (!looksLikeIamPolicy(content))
|
|
1590
|
+
throw new ScanInputError(iamShapeError());
|
|
1591
|
+
let root;
|
|
1592
|
+
try {
|
|
1593
|
+
root = object(JSON.parse(content));
|
|
1594
|
+
}
|
|
1595
|
+
catch {
|
|
1596
|
+
throw new ScanInputError(iamShapeError());
|
|
1597
|
+
}
|
|
1598
|
+
const statements = array(root.Statement ?? root.statement);
|
|
1599
|
+
const resources = [];
|
|
1600
|
+
statements.forEach((statement, index) => {
|
|
1601
|
+
const body = object(statement);
|
|
1602
|
+
if (!effectAllows(body) || !actionIsOnlyWildcard(body))
|
|
1603
|
+
return;
|
|
1604
|
+
const line = iamStatementLine(content, index);
|
|
1605
|
+
resources.push({
|
|
1606
|
+
id: `iam.statement.${index + 1}`,
|
|
1607
|
+
type: "iam:statement",
|
|
1608
|
+
value: { wildcard: true },
|
|
1609
|
+
locate: () => lineLocation(line, `Statement.${index}.Action`),
|
|
1610
|
+
});
|
|
1611
|
+
});
|
|
1612
|
+
if (!resources.length) {
|
|
1613
|
+
resources.push({
|
|
1614
|
+
id: "iam.policy",
|
|
1615
|
+
type: "iam:statement",
|
|
1616
|
+
value: { wildcard: false },
|
|
1617
|
+
locate: () => lineLocation(1, "Statement"),
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
return { format: "iam", resources, warnings: [iamPolicyWarning] };
|
|
1621
|
+
}
|
|
1622
|
+
function iamStatementLine(content, index) {
|
|
1623
|
+
const matches = [...content.matchAll(/"Statement"\s*:/g)];
|
|
1624
|
+
const start = matches[0]?.index ?? 0;
|
|
1625
|
+
const region = content.slice(start);
|
|
1626
|
+
const actions = [...region.matchAll(/"Action"\s*:/g)];
|
|
1627
|
+
const action = actions[index];
|
|
1628
|
+
if (!action || action.index == null)
|
|
1629
|
+
return lineAt(content, start);
|
|
1630
|
+
return lineAt(content, start + action.index);
|
|
1631
|
+
}
|
package/dist/iac/rules.js
CHANGED
|
@@ -351,6 +351,20 @@ export const rules = {
|
|
|
351
351
|
detail: "A Terraform variable default or tfvars assignment gives a non-empty literal to a secret-like name.",
|
|
352
352
|
remediation: "Pass the value from a secret store or leave the default empty. Do not commit a literal secret.",
|
|
353
353
|
},
|
|
354
|
+
TF002: {
|
|
355
|
+
category: "security",
|
|
356
|
+
severity: "high",
|
|
357
|
+
title: "Secret assigned in a Terraform local",
|
|
358
|
+
detail: "A Terraform local assigns a non-empty literal to a secret-like name.",
|
|
359
|
+
remediation: "Pass the value from a secret store or a variable reference. Do not commit a literal secret in locals.",
|
|
360
|
+
},
|
|
361
|
+
SLS001: {
|
|
362
|
+
category: "security",
|
|
363
|
+
severity: "high",
|
|
364
|
+
title: "Secret assigned in serverless environment",
|
|
365
|
+
detail: "A Serverless Framework environment entry assigns a non-empty literal to a secret-like name.",
|
|
366
|
+
remediation: "Reference the value with a Serverless variable such as ${env:TOKEN}. Do not commit a literal secret.",
|
|
367
|
+
},
|
|
354
368
|
};
|
|
355
369
|
export function resourceFindings(resources, format) {
|
|
356
370
|
const findings = [];
|
|
@@ -392,6 +406,18 @@ export function resourceFindings(resources, format) {
|
|
|
392
406
|
checkPipeline(resource, check);
|
|
393
407
|
continue;
|
|
394
408
|
}
|
|
409
|
+
if (format === "serverless") {
|
|
410
|
+
checkServerless(resource, check);
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (format === "iam") {
|
|
414
|
+
check("IAM001", resource.value.wildcard === true, ["Action"]);
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
if (resource.type === "terraform:local") {
|
|
418
|
+
checkTerraformLiterals(resource.value, "local", check, []);
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
395
421
|
if (resource.type === "terraform:module") {
|
|
396
422
|
checkTerraformLiterals(resource.value, "module", check, []);
|
|
397
423
|
continue;
|
|
@@ -473,6 +499,12 @@ function checkPipeline(resource, check) {
|
|
|
473
499
|
else if (resource.type === "pipeline:privileged")
|
|
474
500
|
check("PIPE003", resource.value.present === true, ["privileged"]);
|
|
475
501
|
}
|
|
502
|
+
function checkServerless(resource, check) {
|
|
503
|
+
if (resource.type === "serverless:iam")
|
|
504
|
+
check("IAM001", resource.value.wildcard === true, ["Action"]);
|
|
505
|
+
else if (resource.type === "serverless:env")
|
|
506
|
+
check("SLS001", resource.value.literal === true, ["environment"]);
|
|
507
|
+
}
|
|
476
508
|
function terraformReference(value) {
|
|
477
509
|
const trimmed = value.trim();
|
|
478
510
|
return trimmed.includes("${") || /^var\./.test(trimmed);
|
|
@@ -491,13 +523,15 @@ function moduleCidrExempt(name) {
|
|
|
491
523
|
function checkTerraformLiterals(value, mode, check, path, name = "") {
|
|
492
524
|
if (typeof value === "string" || typeof value === "boolean") {
|
|
493
525
|
const open = value === "0.0.0.0/0" || value === "::/0";
|
|
494
|
-
const exempt = mode === "
|
|
526
|
+
const exempt = mode === "assignment" ? name.toLowerCase() === "egress" : moduleCidrExempt(name);
|
|
495
527
|
if (typeof value === "string")
|
|
496
528
|
check("NET001", open && name !== "" && !exempt, path);
|
|
497
529
|
if (name === "publicly_accessible")
|
|
498
530
|
check("DB001", value === true, path);
|
|
499
531
|
if (mode === "assignment" && typeof value === "string" && secretAssignmentName(name))
|
|
500
532
|
check("TF001", value.trim() !== "" && !terraformReference(value), path);
|
|
533
|
+
if (mode === "local" && typeof value === "string" && secretAssignmentName(name))
|
|
534
|
+
check("TF002", value.trim() !== "" && !terraformReference(value), path);
|
|
501
535
|
return;
|
|
502
536
|
}
|
|
503
537
|
if (Array.isArray(value)) {
|
package/dist/server.js
CHANGED
|
@@ -83,12 +83,14 @@ export function createServer(remote = false) {
|
|
|
83
83
|
"azure-pipelines",
|
|
84
84
|
"cloudbuild",
|
|
85
85
|
"tfvars",
|
|
86
|
+
"serverless",
|
|
87
|
+
"iam",
|
|
86
88
|
"application",
|
|
87
89
|
"auto",
|
|
88
90
|
])
|
|
89
91
|
.optional()
|
|
90
92
|
.default("auto")
|
|
91
|
-
.describe("Content format: terraform, cloudformation, kubernetes, pulumi, compose, dockerfile, github, helm, ansible, bicep, arm, gitlab, azure-pipelines, cloudbuild, tfvars, application, auto."),
|
|
93
|
+
.describe("Content format: terraform, cloudformation, kubernetes, pulumi, compose, dockerfile, github, helm, ansible, bicep, arm, gitlab, azure-pipelines, cloudbuild, tfvars, serverless, iam, application, auto."),
|
|
92
94
|
},
|
|
93
95
|
}, async ({ content, focus, format, minSeverity, maxFindings }) => {
|
|
94
96
|
const botHints = fetchPublicBotHints();
|
|
@@ -136,7 +138,7 @@ export function createServer(remote = false) {
|
|
|
136
138
|
description: (name === "scan_iac_stub"
|
|
137
139
|
? "Compatibility alias for scan_iac; runs the full scanner. "
|
|
138
140
|
: "") +
|
|
139
|
-
"Parse Terraform HCL/JSON, CloudFormation YAML/JSON, Kubernetes manifests, Pulumi YAML, Pulumi programs, or Docker Compose. Includes Dockerfile, GitHub Actions, Helm templates, Ansible playbooks, Bicep files, ARM templates, GitLab CI, Azure Pipelines, Cloud Build, and
|
|
141
|
+
"Parse Terraform HCL/JSON, CloudFormation YAML/JSON, Kubernetes manifests, Pulumi YAML, Pulumi programs, or Docker Compose. Includes Dockerfile, GitHub Actions, Helm templates, Ansible playbooks, Bicep files, ARM templates, GitLab CI, Azure Pipelines, Cloud Build, tfvars, Serverless Framework, and a standalone IAM policy. Helm templates, Ansible facts, Bicep modules, ARM expressions, pipelines, Serverless Framework config, and IAM policies are not executed. Conditions are not evaluated. Terraform module sources are not fetched. Check security, reliability and cost rules with resource locations and remediation. Pulumi programs are scanned as text.",
|
|
140
142
|
annotations: {
|
|
141
143
|
readOnlyHint: true,
|
|
142
144
|
destructiveHint: false,
|
|
@@ -144,7 +146,7 @@ export function createServer(remote = false) {
|
|
|
144
146
|
},
|
|
145
147
|
_meta: { securitySchemes: [{ type: "oauth2", scopes: ["mcp"] }] },
|
|
146
148
|
inputSchema: {
|
|
147
|
-
content: contentSchema.describe("Terraform HCL/JSON, CloudFormation YAML/JSON, Kubernetes YAML/JSON, Pulumi YAML or program text, or Docker Compose. Includes Dockerfile, GitHub Actions, Helm templates, Ansible playbooks, Bicep files, ARM templates, GitLab CI, Azure Pipelines, Cloud Build, and
|
|
149
|
+
content: contentSchema.describe("Terraform HCL/JSON, CloudFormation YAML/JSON, Kubernetes YAML/JSON, Pulumi YAML or program text, or Docker Compose. Includes Dockerfile, GitHub Actions, Helm templates, Ansible playbooks, Bicep files, ARM templates, GitLab CI, Azure Pipelines, Cloud Build, tfvars, Serverless Framework, and a standalone IAM policy."),
|
|
148
150
|
format: z
|
|
149
151
|
.enum([
|
|
150
152
|
"terraform",
|
|
@@ -162,6 +164,8 @@ export function createServer(remote = false) {
|
|
|
162
164
|
"azure-pipelines",
|
|
163
165
|
"cloudbuild",
|
|
164
166
|
"tfvars",
|
|
167
|
+
"serverless",
|
|
168
|
+
"iam",
|
|
165
169
|
"auto",
|
|
166
170
|
])
|
|
167
171
|
.default("auto"),
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.6.
|
|
1
|
+
export declare const VERSION = "0.6.13";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.6.
|
|
1
|
+
export const VERSION = "0.6.13";
|