Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions scripts/check-miner-package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,21 @@ const REQUIRED = [
"Dockerfile",
"schema/miner-goal-spec.schema.json",
];
const FORBIDDEN_PATH = /(^|\/)(\.dev\.vars|\.env|\.npmrc|.*\.pem|.*private.*key.*|.*secret.*)$/i;
// Exact-name dotfiles that are inherently credential-shaped regardless of extension.
const FORBIDDEN_DOTFILE = /(^|\/)(\.dev\.vars|\.env|\.npmrc)$/i;
// A filename merely SUGGESTING secret-shaped content -- a coarse heuristic that's meaningful for data/config
// files (.json, .txt, .yml, .pem, etc.), where the filename is a fair proxy for what's likely inside, but not
// for .js/.ts source (source files are exempted below): a descriptively-named source file that IMPLEMENTS
// secret/credential-handling logic is normal and expected in this codebase, and a real leaked secret VALUE in
// source is already caught unconditionally by FORBIDDEN_CONTENT below, regardless of the file's name.
const FORBIDDEN_KEYWORD = /(^|\/)(.*\.pem|.*private.*key.*|.*secret.*)$/i;
// .ts$ also matches .d.ts (which always ends in the literal characters ".ts").
const SOURCE_FILE = /\.(js|ts)$/i;

function isForbiddenPath(file: string): boolean {
return FORBIDDEN_DOTFILE.test(file) || (!SOURCE_FILE.test(file) && FORBIDDEN_KEYWORD.test(file));
}

// Stale public-package wording the published README must never ship with (#7013). The sibling
// check-mcp-package.ts has always guarded its README against this; the miner-package check did not, so a
// pre-release "private beta"/"preview URL" phrasing could ship in the public `@loopover/miner` README unnoticed.
Expand All @@ -41,7 +55,7 @@ type ReadContentFn = (file: string) => string;
export function validateMinerPackFileList(files: readonly PackedFile[], readContent: ReadContentFn): string[] {
const paths = files.map((file) => (typeof file === "string" ? file : file.path)).sort();
for (const file of paths) {
if (FORBIDDEN_PATH.test(file)) throw new Error(`Forbidden file in miner package: ${file}`);
if (isForbiddenPath(file)) throw new Error(`Forbidden file in miner package: ${file}`);
if (!ALLOWED.some((pattern) => pattern.test(file))) throw new Error(`Unexpected file in miner package: ${file}`);
const content = readContent(file);
if (FORBIDDEN_CONTENT.test(content)) throw new Error(`Secret-like content found in miner package file: ${file}`);
Expand Down
41 changes: 41 additions & 0 deletions test/unit/check-miner-package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,47 @@ describe("check-miner-package script", () => {
expect(result.out).toContain("Forbidden file in miner package: .env");
});

describe("the secret-filename heuristic only applies to non-source files", () => {
it("accepts a source file whose name merely mentions 'secret' or 'private key' (no leaked value inside)", () => {
const result = runChecker({
CHECK_MINER_PACK_TEST_FILES: JSON.stringify([
"package.json",
"bin/loopover-miner.js",
"lib/secret-helper.js",
"lib/secret-helper.d.ts",
"lib/private-key-rotation.js",
"DEPLOYMENT.md",
"Dockerfile",
"docs/coding-agent-driver.md",
"schema/miner-goal-spec.schema.json",
]),
CHECK_MINER_PACK_TEST_CONTENT: "console.log('this file discusses secrets but holds no secret value');",
});
expect(result.status).toBe(0);
expect(result.out).toMatch(/^Miner package dry-run ok:/);
expect(result.out).toContain("lib/secret-helper.js");
expect(result.out).toContain("lib/private-key-rotation.js");
});

it("still rejects a non-code file whose name suggests it IS a secret (.pem, secrets.json)", () => {
const pem = runChecker({ CHECK_MINER_PACK_TEST_FILES: JSON.stringify(["my-private-key.pem"]) });
expect(pem.status).toBe(1);
expect(pem.out).toContain("Forbidden file in miner package: my-private-key.pem");

const json = runChecker({ CHECK_MINER_PACK_TEST_FILES: JSON.stringify(["config/secrets.json"]) });
expect(json.status).toBe(1);
expect(json.out).toContain("Forbidden file in miner package: config/secrets.json");
});

it("still rejects the exact-name dotfiles (.env/.npmrc/.dev.vars) regardless of this change", () => {
for (const dotfile of [".env", ".npmrc", ".dev.vars"]) {
const result = runChecker({ CHECK_MINER_PACK_TEST_FILES: JSON.stringify([dotfile]) });
expect(result.status).toBe(1);
expect(result.out).toContain(`Forbidden file in miner package: ${dotfile}`);
}
});
});

it("rejects a README carrying stale public-package wording (#7013)", () => {
const result = runChecker({
CHECK_MINER_PACK_TEST_FILES: JSON.stringify([
Expand Down