Repository navigation
Expand file tree
/
Copy pathvalidate-secrets.test.ts
More file actions
105 lines (89 loc) · 3.46 KB
/
Copy pathvalidate-secrets.test.ts
File metadata and controls
105 lines (89 loc) · 3.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// Copyright 2023-present Eser Ozvataf and other contributors. All rights reserved. Apache-2.0 license.
/**
* validate-secrets has two implementations: the TypeScript checkFile (what
* `run` and `--fix` use) and the Go ValidateSecrets behind the FFI (what
* `validator.validate` uses). They must agree finding for finding, or the gate
* and the fixer disagree — on 2026-09-17 they reported 84 and 2 issues on the
* same tree. This runs both over one fixture and compares.
*
* @module
*/
import * as assert from "@std/assert";
import * as path from "@std/path";
import { runtime } from "@eserstack/standards/cross-runtime";
import { ensureLib, getLib } from "./ffi-client.ts";
import { tool } from "./validate-secrets.ts";
const HEADER =
"// Copyright 2023-present Eser Ozvataf and other contributors. All rights reserved. Apache-2.0 license.\n";
const FIXTURE: Record<string, string> = {
"src/config.ts": HEADER + "export const apiKey = 'abcdefghijklmnop';\n" +
'export const password = "correct horse battery";\n' +
"export const name = 'service';\n",
"src/notes.md": "The token: abcdefghijklmnop rotates daily.\n" +
"password = unquotedvalue123\n",
"src/keys.pem": "-----BEGIN RSA PRIVATE KEY-----\nMIIE...\n",
"src/aws.txt": "id AKIAIOSFODNN7EXAMPLE\n",
"src/config.test.ts": HEADER + "const secret = 'abcdefghijklmnop';\n",
"docs/skills/security.md": "Never do this: password = 'hunter2hunter2'\n",
};
/** Excluded exactly the way .eser/manifest.yml excludes the skill docs. */
const EXCLUDE = ["docs/skills/"];
const findings = (
issues: ReadonlyArray<{ path?: string; file?: string; line?: number }>,
root: string,
): string[] =>
issues
.map((i) => {
const p = (i.path ?? i.file ?? "").replace(/^\.\//, "");
const rel = path.isAbsolute(p) ? path.relative(root, p) : p;
return `${rel.replaceAll("\\", "/")}:${i.line ?? 0}`;
})
.sort();
const makeFixture = async (): Promise<string> => {
const root = await runtime.fs.makeTempDir({
prefix: "validate-secrets-parity-",
});
for (const [rel, body] of Object.entries(FIXTURE)) {
const full = path.join(root, rel);
await runtime.fs.mkdir(path.dirname(full), { recursive: true });
await runtime.fs.writeTextFile(full, body);
}
return root;
};
// sanitizeResources: false — file-tool.ts transitively loads the FFI DynamicLibrary
Deno.test({
name: "validate-secrets: Go and TypeScript implementations agree",
sanitizeResources: false,
sanitizeOps: false,
async fn() {
await ensureLib();
const lib = getLib();
const nativeDisabled = Deno.env.get("ESER_AJAN_NATIVE") === "disabled";
if (lib === null && nativeDisabled) {
// Native deliberately off: only the TypeScript half can run here.
return;
}
assert.assert(
lib !== null,
"native library not loaded — build it first (deno task cli preflight or build-native-lib)",
);
const root = await makeFixture();
try {
const ts = await tool.run({ root, exclude: EXCLUDE });
const go = await tool.validator.validate({
root,
options: { exclude: EXCLUDE },
});
const expected = [
"src/aws.txt:1",
"src/config.ts:2",
"src/config.ts:3",
"src/keys.pem:1",
];
assert.assertEquals(findings(ts.issues, root), expected, "TypeScript");
assert.assertEquals(findings(go.issues, root), expected, "Go");
} finally {
await runtime.fs.remove(root, { recursive: true });
}
},
});