Skip to content

Commit 2cc7d89

Browse files
authored
Merge pull request #21 from dash14/feature/report-restrict-example
Add restrict mode config example to audit report
2 parents 1f30bb3 + e1b92f9 commit 2cc7d89

File tree

4 files changed

+189
-2
lines changed

4 files changed

+189
-2
lines changed

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,16 @@ test_audit_mode: ## Run audit mode tests
6767
@$(MAKE) clean
6868

6969
.PHONY: test_unit
70-
test_unit: test_legacy test_qjs ## Run unit tests
70+
test_unit: test_legacy test_report test_qjs ## Run unit tests
7171

7272
.PHONY: test_legacy
7373
test_legacy: ## Run legacy rules unit tests
7474
@node --test setup/lib/legacy-rules.test.mjs
7575

76+
.PHONY: test_report
77+
test_report: ## Run report unit tests
78+
@node --test report/lib/build-example.test.mjs
79+
7680
.PHONY: test_qjs
7781
test_qjs: ## Run unit tests in Docker
7882
@docker build -t buildcage-qjs-test docker

report/lib/build-example.mjs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const ruleTypeToParam = {
2+
HTTPS: "allowed_https_rules",
3+
HTTP: "allowed_http_rules",
4+
IP: "allowed_ip_rules",
5+
};
6+
7+
/**
8+
* Build a restrict-mode YAML configuration example from audited rows.
9+
* Returns a markdown string wrapped in <details> tags, or "" if no rows.
10+
*
11+
* @param {Array<{host: string, port: string, ruleType: string}>} auditedRows
12+
* @returns {string}
13+
*/
14+
export function buildRestrictExample(auditedRows, actionRepo) {
15+
if (!auditedRows || auditedRows.length === 0) return "";
16+
17+
// Group by ruleType, preserving order of first appearance
18+
const groups = new Map();
19+
for (const r of auditedRows) {
20+
const param = ruleTypeToParam[r.ruleType];
21+
if (!param) continue;
22+
if (!groups.has(param)) groups.set(param, []);
23+
groups.get(param).push(`${r.host}:${r.port}`);
24+
}
25+
26+
if (groups.size === 0) return "";
27+
28+
// Build YAML lines
29+
let yaml = "";
30+
yaml += "- name: Start Buildcage in restrict mode\n";
31+
yaml += ` uses: ${actionRepo}/setup@v1\n`;
32+
yaml += " with:\n";
33+
yaml += " proxy_mode: restrict\n";
34+
for (const [param, rules] of groups) {
35+
yaml += ` ${param}: >-\n`;
36+
for (const rule of rules) {
37+
yaml += ` ${rule}\n`;
38+
}
39+
}
40+
41+
let md = "\n<details>\n";
42+
md += "<summary>🛡️ Switch to restrict mode</summary>\n\n";
43+
md += "```yaml\n";
44+
md += yaml;
45+
md += "```\n\n";
46+
md += "</details>\n";
47+
return md;
48+
}

report/lib/build-example.test.mjs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { describe, it } from "node:test";
2+
import assert from "node:assert/strict";
3+
import { buildRestrictExample } from "./build-example.mjs";
4+
5+
const REPO = "dash14/buildcage";
6+
7+
function wrap(yaml) {
8+
return (
9+
"\n<details>\n" +
10+
"<summary>🛡️ Switch to restrict mode</summary>\n\n" +
11+
"```yaml\n" +
12+
yaml +
13+
"```\n\n" +
14+
"</details>\n"
15+
);
16+
}
17+
18+
describe("buildRestrictExample", () => {
19+
it("empty array → empty string", () => {
20+
assert.equal(buildRestrictExample([], REPO), "");
21+
});
22+
23+
it("null/undefined → empty string", () => {
24+
assert.equal(buildRestrictExample(null, REPO), "");
25+
assert.equal(buildRestrictExample(undefined, REPO), "");
26+
});
27+
28+
it("HTTPS only entries", () => {
29+
const rows = [
30+
{ host: "registry.npmjs.org", port: "443", ruleType: "HTTPS", count: 5 },
31+
{ host: "github.com", port: "443", ruleType: "HTTPS", count: 2 },
32+
];
33+
assert.equal(
34+
buildRestrictExample(rows, REPO),
35+
wrap(
36+
[
37+
"- name: Start Buildcage in restrict mode",
38+
` uses: ${REPO}/setup@v1`,
39+
" with:",
40+
" proxy_mode: restrict",
41+
" allowed_https_rules: >-",
42+
" registry.npmjs.org:443",
43+
" github.com:443",
44+
].join("\n") + "\n",
45+
)
46+
);
47+
});
48+
49+
it("HTTP + HTTPS mixed entries", () => {
50+
const rows = [
51+
{ host: "registry.npmjs.org", port: "443", ruleType: "HTTPS", count: 3 },
52+
{ host: "deb.debian.org", port: "80", ruleType: "HTTP", count: 1 },
53+
];
54+
assert.equal(
55+
buildRestrictExample(rows, REPO),
56+
wrap(
57+
[
58+
"- name: Start Buildcage in restrict mode",
59+
` uses: ${REPO}/setup@v1`,
60+
" with:",
61+
" proxy_mode: restrict",
62+
" allowed_https_rules: >-",
63+
" registry.npmjs.org:443",
64+
" allowed_http_rules: >-",
65+
" deb.debian.org:80",
66+
].join("\n") + "\n",
67+
)
68+
);
69+
});
70+
71+
it("IP entries", () => {
72+
const rows = [
73+
{ host: "192.168.1.1", port: "443", ruleType: "IP", count: 1 },
74+
];
75+
assert.equal(
76+
buildRestrictExample(rows, REPO),
77+
wrap(
78+
[
79+
"- name: Start Buildcage in restrict mode",
80+
` uses: ${REPO}/setup@v1`,
81+
" with:",
82+
" proxy_mode: restrict",
83+
" allowed_ip_rules: >-",
84+
" 192.168.1.1:443",
85+
].join("\n") + "\n",
86+
)
87+
);
88+
});
89+
90+
it("all three rule types", () => {
91+
const rows = [
92+
{ host: "example.com", port: "443", ruleType: "HTTPS", count: 2 },
93+
{ host: "example.com", port: "80", ruleType: "HTTP", count: 1 },
94+
{ host: "10.0.0.1", port: "8080", ruleType: "IP", count: 1 },
95+
];
96+
assert.equal(
97+
buildRestrictExample(rows, REPO),
98+
wrap(
99+
[
100+
"- name: Start Buildcage in restrict mode",
101+
` uses: ${REPO}/setup@v1`,
102+
" with:",
103+
" proxy_mode: restrict",
104+
" allowed_https_rules: >-",
105+
" example.com:443",
106+
" allowed_http_rules: >-",
107+
" example.com:80",
108+
" allowed_ip_rules: >-",
109+
" 10.0.0.1:8080",
110+
].join("\n") + "\n",
111+
)
112+
);
113+
});
114+
115+
it("uses custom actionRepo", () => {
116+
const rows = [
117+
{ host: "example.com", port: "443", ruleType: "HTTPS", count: 1 },
118+
];
119+
assert.equal(
120+
buildRestrictExample(rows, "myorg/myrepo"),
121+
wrap(
122+
[
123+
"- name: Start Buildcage in restrict mode",
124+
" uses: myorg/myrepo/setup@v1",
125+
" with:",
126+
" proxy_mode: restrict",
127+
" allowed_https_rules: >-",
128+
" example.com:443",
129+
].join("\n") + "\n",
130+
)
131+
);
132+
});
133+
});

report/main.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { execFileSync } from "node:child_process";
22
import { appendFileSync } from "node:fs";
33
import { join, dirname } from "node:path";
44
import { fileURLToPath } from "node:url";
5+
import { buildRestrictExample } from "./lib/build-example.mjs";
56

67
const __dirname = dirname(fileURLToPath(import.meta.url));
78

@@ -62,6 +63,7 @@ function markdownTable(rows, { showReason = false } = {}) {
6263
return lines.join("\n");
6364
}
6465

66+
const actionRepo = process.env.GITHUB_ACTION_REPOSITORY || "dash14/buildcage";
6567
const isAudit = report.mode === "audit";
6668
let markdown = `## Outbound Traffic Report during Docker Build (${report.mode} mode)\n\n`;
6769

@@ -70,6 +72,7 @@ if (isAudit) {
7072
if (audited.length > 0) {
7173
markdown += "### 📋 Audited Hosts\n\n" + markdownTable(audited) + "\n";
7274
}
75+
markdown += buildRestrictExample(audited, actionRepo);
7376
const blocked = report.sections.blocked || [];
7477
if (blocked.length > 0) {
7578
if (audited.length > 0) markdown += "\n";
@@ -91,7 +94,6 @@ if (isAudit) {
9194

9295
markdown += "\n<sub>*Note: HTTP rules are based on the Host header, HTTPS rules on SNI, and IP rules on the destination IP address.*</sub>\n";
9396

94-
const actionRepo = process.env.GITHUB_ACTION_REPOSITORY || "dash14/buildcage";
9597
markdown += `\n*Reported by [Buildcage](https://github.com/${actionRepo})*\n`;
9698

9799
// Write Job Summary

0 commit comments

Comments
 (0)