Skip to content

Commit e98b73c

Browse files
Merge pull request #7 from jonathansantilli/alert-autofix-5
Potential fix for code scanning alert no. 5: Insecure randomness
2 parents 5adf557 + b2c323a commit e98b73c

13 files changed

Lines changed: 82 additions & 35 deletions

src/layer4-remediation/backup-manager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createHash } from "node:crypto";
1+
import { createHash, randomBytes } from "node:crypto";
22
import {
33
existsSync,
44
mkdirSync,
@@ -69,7 +69,7 @@ function backupRoot(projectRoot: string): string {
6969

7070
function sessionIdFromNow(): string {
7171
const stamp = new Date().toISOString().replaceAll(":", "-").replaceAll(".", "-");
72-
const nonce = Math.random().toString(16).slice(2, 8);
72+
const nonce = randomBytes(3).toString("hex");
7373
return `${stamp}-${nonce}`;
7474
}
7575

src/scan-target/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export function sanitizePathSegment(value: string): string {
5858

5959
export function preserveTailSegments(pathname: string, count: number): string {
6060
const segments = pathname
61-
.split("/")
61+
.split(/[\\/]+/u)
6262
.filter((segment) => segment.length > 0)
6363
.map((segment) => sanitizePathSegment(segment));
6464
if (segments.length === 0) {

tests/cli/init-command.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { resolve } from "node:path";
12
import { describe, expect, it } from "vitest";
23
import { createCli, type CliDeps } from "../../src/cli";
34
import type { CodeGateConfig } from "../../src/config";
@@ -57,12 +58,14 @@ function makeDeps(overrides: Partial<CliDeps>): CliDeps {
5758

5859
describe("init command", () => {
5960
it("writes default config to ~/.codegate/config.json", async () => {
61+
const home = "/tmp/codegate-home";
6062
let exitCode = -1;
6163
let writtenPath = "";
6264
let content = "";
6365
const cli = createCli(
6466
"0.2.2",
6567
makeDeps({
68+
homeDir: () => home,
6669
writeFile: (path, value) => {
6770
writtenPath = path;
6871
content = value;
@@ -74,7 +77,7 @@ describe("init command", () => {
7477
);
7578

7679
await cli.parseAsync(["node", "codegate", "init"]);
77-
expect(writtenPath).toBe("/tmp/codegate-home/.codegate/config.json");
80+
expect(writtenPath).toBe(resolve(home, ".codegate", "config.json"));
7881
expect(content).toContain('"severity_threshold"');
7982
expect(content).toContain('"scan_state_path"');
8083
expect(exitCode).toBe(0);

tests/cli/remediation-flags.test.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createCli, type CliDeps } from "../../src/cli";
33
import type { CodeGateConfig } from "../../src/config";
44
import type { Finding } from "../../src/types/finding";
55
import type { CodeGateReport } from "../../src/types/report";
6+
import { normalizeLines } from "../helpers/path";
67

78
const BASE_CONFIG: CodeGateConfig = {
89
severity_threshold: "high",
@@ -178,17 +179,18 @@ describe("task 23 remediation flags", () => {
178179
});
179180

180181
await cli.parseAsync(["node", "codegate", "scan", ".", "--remediate"]);
182+
const normalized = normalizeLines(printed);
181183
expect(runRemediation).toHaveBeenCalledTimes(1);
182-
expect(printed.some((line) => line.includes("Remediation summary"))).toBe(true);
183-
expect(printed.some((line) => line.includes("Planned changes: 2"))).toBe(true);
184-
expect(printed.some((line) => line.includes("Applied changes: 2"))).toBe(true);
184+
expect(normalized.some((line) => line.includes("Remediation summary"))).toBe(true);
185+
expect(normalized.some((line) => line.includes("Planned changes: 2"))).toBe(true);
186+
expect(normalized.some((line) => line.includes("Applied changes: 2"))).toBe(true);
185187
expect(
186-
printed.some((line) => line.includes(".codegate-backup/2026-03-01T12-00-00-aaaaaa")),
188+
normalized.some((line) => line.includes(".codegate-backup/2026-03-01T12-00-00-aaaaaa")),
187189
).toBe(true);
188-
expect(printed.some((line) => line.includes("codegate undo"))).toBe(true);
189-
expect(printed.some((line) => line.includes("remove_field"))).toBe(true);
190+
expect(normalized.some((line) => line.includes("codegate undo"))).toBe(true);
191+
expect(normalized.some((line) => line.includes("remove_field"))).toBe(true);
190192
expect(
191-
printed.some((line) => line.includes("ENV_OVERRIDE-.mcp.json-env.OPENAI_BASE_URL")),
193+
normalized.some((line) => line.includes("ENV_OVERRIDE-.mcp.json-env.OPENAI_BASE_URL")),
192194
).toBe(true);
193195
});
194196

tests/commands/scan-command-helpers.test.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { resolve } from "node:path";
12
import { describe, expect, it } from "vitest";
23
import {
34
noEligibleDeepResourceNotes,
@@ -7,6 +8,7 @@ import {
78
} from "../../src/commands/scan-command/helpers";
89
import type { ScanCommandOptions } from "../../src/commands/scan-command";
910
import type { CodeGateReport } from "../../src/types/report";
11+
import { normalizeLines } from "../helpers/path";
1012

1113
function emptyReport(): CodeGateReport {
1214
return {
@@ -65,8 +67,9 @@ describe("scan command helpers", () => {
6567
});
6668

6769
it("formats remediation summary lines with backup and action details", () => {
70+
const scanTarget = "/tmp/codegate-demo";
6871
const result = remediationSummaryLines({
69-
scanTarget: "/tmp/codegate-demo",
72+
scanTarget,
7073
options: { remediate: true } satisfies ScanCommandOptions,
7174
before: {
7275
...emptyReport(),
@@ -95,13 +98,18 @@ describe("scan command helpers", () => {
9598
],
9699
},
97100
});
101+
const normalized = normalizeLines(result);
98102

99-
expect(result).toContain("Remediation summary:");
100-
expect(result).toContain("Mode: remediate");
101-
expect(result).toContain("Findings before remediation: 3");
102-
expect(result).toContain("Findings after remediation: 1");
103-
expect(result).toContain("Backup session: /tmp/codegate-demo/.codegate-backup/session-123");
104-
expect(result).toContain("- remove_field -> /tmp/codegate-demo/.mcp.json (F-1)");
103+
expect(normalized).toContain("Remediation summary:");
104+
expect(normalized).toContain("Mode: remediate");
105+
expect(normalized).toContain("Findings before remediation: 3");
106+
expect(normalized).toContain("Findings after remediation: 1");
107+
expect(normalized).toContain(
108+
`Backup session: ${resolve(scanTarget, ".codegate-backup", "session-123").replaceAll("\\", "/")}`,
109+
);
110+
expect(normalized).toContain(
111+
`- remove_field -> ${resolve(scanTarget, ".mcp.json").replaceAll("\\", "/")} (F-1)`,
112+
);
105113
});
106114

107115
it("returns the stock no-resource deep scan notes", () => {

tests/fixtures/fixtures.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ describe("task 06 fixture corpus", () => {
1919
it("contains clean project fixtures", () => {
2020
const cleanRoot = resolve(root, "clean-projects");
2121
expect(existsSync(cleanRoot)).toBe(true);
22-
expect(readdirSync(cleanRoot).length).toBeGreaterThanOrEqual(2);
22+
const projects = readdirSync(cleanRoot);
23+
expect(projects.length).toBeGreaterThanOrEqual(1);
24+
expect(projects).toContain("clean-claude");
2325
});
2426

2527
it("contains malformed parsing fixtures", () => {

tests/helpers/path.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { resolve } from "node:path";
2+
3+
export function normalizeSlashes(value: string): string {
4+
return value.replaceAll("\\", "/");
5+
}
6+
7+
export function resolveForHost(...paths: string[]): string {
8+
return normalizeSlashes(resolve(...paths));
9+
}
10+
11+
export function normalizeLines(lines: string[]): string[] {
12+
return lines.map(normalizeSlashes);
13+
}

tests/layer1/artifact-candidate-discovery.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
createScanDiscoveryContext,
1010
} from "../../src/scan";
1111
import { resolveScanTarget } from "../../src/scan-target";
12+
import { normalizeSlashes } from "../helpers/path";
1213

1314
const BASE_CONFIG: CodeGateConfig = {
1415
severity_threshold: "high",
@@ -59,7 +60,7 @@ describe("artifact candidate discovery", () => {
5960
report.findings.some(
6061
(finding) =>
6162
finding.rule_id === "rule-file-remote-shell" &&
62-
finding.file_path === "skills/security-review/SKILL.md",
63+
normalizeSlashes(finding.file_path) === "skills/security-review/SKILL.md",
6364
),
6465
).toBe(true);
6566
});
@@ -111,7 +112,7 @@ describe("artifact candidate discovery", () => {
111112
report.findings.some(
112113
(finding) =>
113114
finding.rule_id === "plugin-manifest-insecure-source-url" &&
114-
finding.file_path === "manifests/plugins.json",
115+
normalizeSlashes(finding.file_path) === "manifests/plugins.json",
115116
),
116117
).toBe(true);
117118
});
@@ -151,7 +152,7 @@ describe("artifact candidate discovery", () => {
151152
report.findings.some(
152153
(finding) =>
153154
finding.rule_id === "rule-file-remote-shell" &&
154-
finding.file_path === "skills/security-review/nested/payload.txt",
155+
normalizeSlashes(finding.file_path) === "skills/security-review/nested/payload.txt",
155156
),
156157
).toBe(true);
157158
});

tests/layer1/file-walker.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { tmpdir } from "node:os";
33
import { join } from "node:path";
44
import { afterEach, describe, expect, it } from "vitest";
55
import { walkProjectTree } from "../../src/layer1-discovery/file-walker";
6+
import { normalizeSlashes } from "../helpers/path";
67

78
const tempDirs: string[] = [];
89

@@ -29,9 +30,10 @@ describe("task 09 file walker", () => {
2930
writeFileSync(join(root, "README.md"), "ok");
3031

3132
const result = walkProjectTree(root);
32-
expect(result.files.some((file) => file.endsWith("README.md"))).toBe(true);
33-
expect(result.files.some((file) => file.includes("node_modules"))).toBe(false);
34-
expect(result.files.some((file) => file.includes(".git/hooks/pre-commit"))).toBe(true);
33+
const files = result.files.map(normalizeSlashes);
34+
expect(files.some((file) => file.endsWith("README.md"))).toBe(true);
35+
expect(files.some((file) => file.includes("node_modules"))).toBe(false);
36+
expect(files.some((file) => file.includes(".git/hooks/pre-commit"))).toBe(true);
3537
});
3638

3739
it("detects symlink escape outside project root", () => {

tests/layer1/tool-detector.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { join } from "node:path";
12
import { describe, expect, it } from "vitest";
23
import {
34
detectTools,
@@ -55,12 +56,11 @@ describe("task 10 tool detector", () => {
5556
});
5657

5758
it("detects GitHub Copilot extension installation", () => {
59+
const extensionsDir = join("/Users/tester", ".vscode", "extensions");
5860
const detections = detectTools(
5961
makeDeps({
6062
listDirectory: (path) =>
61-
path === "/Users/tester/.vscode/extensions"
62-
? ["github.copilot-1.2.3", "some.other-ext-0.1.0"]
63-
: [],
63+
path === extensionsDir ? ["github.copilot-1.2.3", "some.other-ext-0.1.0"] : [],
6464
}),
6565
);
6666
const copilot = byName(detections, "github-copilot");

0 commit comments

Comments
 (0)