bootproof 0.1.0 → 0.3.0
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/README.md +84 -8
- package/dist/cli.js +230 -16
- package/dist/diagnosis.js +13 -1
- package/dist/exec.js +21 -4
- package/dist/infer.js +281 -32
- package/dist/plan.d.ts +2 -0
- package/dist/plan.js +47 -7
- package/dist/proof.d.ts +1 -1
- package/dist/proof.js +2 -2
- package/dist/remote.d.ts +12 -1
- package/dist/remote.js +62 -18
- package/dist/repair.d.ts +110 -0
- package/dist/repair.js +857 -0
- package/dist/run.d.ts +3 -1
- package/dist/run.js +182 -20
- package/dist/taxonomy.d.ts +1 -0
- package/dist/taxonomy.js +28 -4
- package/dist/types.d.ts +18 -2
- package/docs/CI_ACTION.md +4 -3
- package/docs/FAILURE_TAXONOMY.md +3 -1
- package/docs/HONESTY_CONTRACT.md +30 -1
- package/docs/REAL_REPO_EVIDENCE.md +77 -0
- package/docs/RELEASE_CHECKLIST.md +9 -1
- package/docs/REPAIR_RECEIPT.md +178 -0
- package/package.json +6 -3
package/dist/remote.js
CHANGED
|
@@ -1,51 +1,91 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
|
+
const REMOTE_PROVIDERS = {
|
|
5
|
+
"github.com": "github",
|
|
6
|
+
"gitlab.com": "gitlab",
|
|
7
|
+
"bitbucket.org": "bitbucket",
|
|
8
|
+
"codeberg.org": "codeberg",
|
|
9
|
+
};
|
|
4
10
|
export function isRemoteTarget(value) {
|
|
5
11
|
return /^[a-z][a-z0-9+.-]*:\/\//i.test(value) || /^git@/i.test(value);
|
|
6
12
|
}
|
|
7
|
-
export function
|
|
13
|
+
export function parseRemoteTarget(value) {
|
|
8
14
|
let url;
|
|
9
15
|
try {
|
|
10
16
|
url = new URL(value);
|
|
11
17
|
}
|
|
12
18
|
catch {
|
|
13
|
-
throw new Error("Remote targets must be full HTTPS
|
|
19
|
+
throw new Error("Remote targets must be full HTTPS Git repository URLs.");
|
|
14
20
|
}
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
const host = url.hostname.toLowerCase();
|
|
22
|
+
const provider = REMOTE_PROVIDERS[host];
|
|
23
|
+
if (url.protocol !== "https:" || !provider) {
|
|
24
|
+
throw new Error("Remote mode accepts credential-free HTTPS repositories from GitHub, GitLab, Bitbucket, or Codeberg.");
|
|
17
25
|
}
|
|
18
26
|
if (url.username || url.password || url.port || url.search || url.hash) {
|
|
19
27
|
throw new Error("Remote URLs must not contain credentials, custom ports, query strings, or fragments.");
|
|
20
28
|
}
|
|
21
29
|
const parts = url.pathname.split("/").filter(Boolean);
|
|
22
|
-
if (parts.length !== 2) {
|
|
23
|
-
throw new Error(
|
|
30
|
+
if (parts.length < 2 || (provider !== "gitlab" && parts.length !== 2)) {
|
|
31
|
+
throw new Error(provider === "gitlab"
|
|
32
|
+
? "GitLab URLs must identify a namespace and repository."
|
|
33
|
+
: `Remote ${provider} URLs must identify exactly one namespace and repository.`);
|
|
24
34
|
}
|
|
25
|
-
const
|
|
26
|
-
const
|
|
35
|
+
const repo = parts.at(-1).replace(/\.git$/i, "");
|
|
36
|
+
const namespaceParts = parts.slice(0, -1);
|
|
27
37
|
const safeSegment = /^[A-Za-z0-9_.-]+$/;
|
|
28
|
-
if (!
|
|
29
|
-
|
|
38
|
+
if (!namespaceParts.length ||
|
|
39
|
+
[...namespaceParts, repo].some(segment => !safeSegment.test(segment) || segment === "." || segment === ".." || segment === "-")) {
|
|
40
|
+
throw new Error("Remote namespace or repository names contain unsupported characters.");
|
|
30
41
|
}
|
|
42
|
+
const namespace = namespaceParts.join("/");
|
|
31
43
|
return {
|
|
32
44
|
originalUrl: value,
|
|
33
|
-
canonicalUrl: `https
|
|
34
|
-
|
|
45
|
+
canonicalUrl: `https://${host}/${namespace}/${repo}.git`,
|
|
46
|
+
provider,
|
|
47
|
+
host,
|
|
48
|
+
namespace,
|
|
35
49
|
repo,
|
|
36
50
|
};
|
|
37
51
|
}
|
|
38
|
-
export function
|
|
39
|
-
const remote =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
export function parseGithubRemote(value) {
|
|
53
|
+
const remote = parseRemoteTarget(value);
|
|
54
|
+
if (remote.provider !== "github") {
|
|
55
|
+
throw new Error("Expected a public HTTPS GitHub repository URL.");
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
originalUrl: remote.originalUrl,
|
|
59
|
+
canonicalUrl: remote.canonicalUrl,
|
|
60
|
+
owner: remote.namespace,
|
|
61
|
+
repo: remote.repo,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function cloneRemoteTarget(value, cwd) {
|
|
65
|
+
const remote = parseRemoteTarget(value);
|
|
66
|
+
const namespaceRoot = path.join(cwd, ".bootproof", "remotes", remote.host, ...remote.namespace.split("/"));
|
|
67
|
+
fs.mkdirSync(namespaceRoot, { recursive: true });
|
|
68
|
+
const runRoot = fs.mkdtempSync(path.join(namespaceRoot, `${remote.repo}-`));
|
|
43
69
|
const repoPath = path.join(runRoot, "repo");
|
|
70
|
+
const isolatedGitConfig = path.join(runRoot, "gitconfig");
|
|
71
|
+
fs.writeFileSync(isolatedGitConfig, "");
|
|
44
72
|
try {
|
|
45
|
-
execFileSync("git", ["clone", "--depth", "1", "--single-branch", "--no-tags", "--", remote.canonicalUrl, repoPath], {
|
|
73
|
+
execFileSync("git", ["-c", "credential.helper=", "clone", "--depth", "1", "--single-branch", "--no-tags", "--", remote.canonicalUrl, repoPath], {
|
|
74
|
+
encoding: "utf8",
|
|
75
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
76
|
+
env: {
|
|
77
|
+
...process.env,
|
|
78
|
+
GIT_ASKPASS: "",
|
|
79
|
+
GIT_CONFIG_GLOBAL: isolatedGitConfig,
|
|
80
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
81
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
fs.rmSync(isolatedGitConfig, { force: true });
|
|
46
85
|
const marker = {
|
|
47
86
|
schema: "bootproof/remote-source/v1",
|
|
48
87
|
canonicalUrl: remote.canonicalUrl,
|
|
88
|
+
provider: remote.provider,
|
|
49
89
|
repoDirectory: "repo",
|
|
50
90
|
};
|
|
51
91
|
fs.writeFileSync(path.join(runRoot, "source.json"), JSON.stringify(marker, null, 2) + "\n");
|
|
@@ -57,6 +97,10 @@ export function cloneGithubRemote(value, cwd) {
|
|
|
57
97
|
}
|
|
58
98
|
return { ...remote, repoPath };
|
|
59
99
|
}
|
|
100
|
+
export function cloneGithubRemote(value, cwd) {
|
|
101
|
+
parseGithubRemote(value);
|
|
102
|
+
return cloneRemoteTarget(value, cwd);
|
|
103
|
+
}
|
|
60
104
|
export function managedRemoteSource(repoPath) {
|
|
61
105
|
const markerPath = path.join(path.dirname(path.resolve(repoPath)), "source.json");
|
|
62
106
|
try {
|
package/dist/repair.d.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { Attestation, FailureClass, PackageManager } from "./types.js";
|
|
2
|
+
export type RepairKind = "repo-diff" | "plan-step" | "environment";
|
|
3
|
+
export interface RepairReceipt {
|
|
4
|
+
schema: "bootproof/repair-receipt/v1";
|
|
5
|
+
tool: string;
|
|
6
|
+
repo: {
|
|
7
|
+
remote: string | null;
|
|
8
|
+
commit: string | null;
|
|
9
|
+
dirty: boolean | null;
|
|
10
|
+
};
|
|
11
|
+
environment: {
|
|
12
|
+
os: string;
|
|
13
|
+
arch: string;
|
|
14
|
+
node: string;
|
|
15
|
+
};
|
|
16
|
+
failure: {
|
|
17
|
+
class: FailureClass;
|
|
18
|
+
beforeAttestationSha256: string;
|
|
19
|
+
};
|
|
20
|
+
repair: {
|
|
21
|
+
id: string;
|
|
22
|
+
kind: RepairKind;
|
|
23
|
+
description: string;
|
|
24
|
+
diff: string | null;
|
|
25
|
+
filesChanged: string[];
|
|
26
|
+
fileChanges: RepairReceiptFileChange[];
|
|
27
|
+
preconditions: RepairReceiptPrecondition[];
|
|
28
|
+
planDelta: string | null;
|
|
29
|
+
envDelta: string | null;
|
|
30
|
+
};
|
|
31
|
+
verification: {
|
|
32
|
+
before: {
|
|
33
|
+
booted: false;
|
|
34
|
+
failureClass: FailureClass;
|
|
35
|
+
attestationSha256: string;
|
|
36
|
+
};
|
|
37
|
+
after: {
|
|
38
|
+
booted: true;
|
|
39
|
+
healthObservation: string;
|
|
40
|
+
attestationSha256: string;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
startedAt: string;
|
|
44
|
+
finishedAt: string;
|
|
45
|
+
signer: {
|
|
46
|
+
publicKey: string;
|
|
47
|
+
algorithm: "ed25519";
|
|
48
|
+
} | null;
|
|
49
|
+
signature: string | null;
|
|
50
|
+
}
|
|
51
|
+
export interface RepairResult {
|
|
52
|
+
schema: "bootproof/repair-result/v1";
|
|
53
|
+
repaired: boolean;
|
|
54
|
+
failureClass: FailureClass | null;
|
|
55
|
+
repairId: string | null;
|
|
56
|
+
receiptPath: string | null;
|
|
57
|
+
patchPath: string | null;
|
|
58
|
+
afterAttestationPath: string | null;
|
|
59
|
+
explanation: string;
|
|
60
|
+
}
|
|
61
|
+
export interface RepairApplyResult {
|
|
62
|
+
schema: "bootproof/repair-apply-result/v1";
|
|
63
|
+
applied: boolean;
|
|
64
|
+
receiptPath: string;
|
|
65
|
+
filesChanged: string[];
|
|
66
|
+
explanation: string;
|
|
67
|
+
}
|
|
68
|
+
export interface RepairOptions {
|
|
69
|
+
provider?: "docker" | "local";
|
|
70
|
+
unsafeLocal: boolean;
|
|
71
|
+
timeoutMs: number;
|
|
72
|
+
port?: number;
|
|
73
|
+
remoteSource?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface RepairFileChange {
|
|
76
|
+
path: string;
|
|
77
|
+
before: string | null;
|
|
78
|
+
after: string;
|
|
79
|
+
}
|
|
80
|
+
export interface RepairReceiptFileChange {
|
|
81
|
+
path: string;
|
|
82
|
+
beforeSha256: string | null;
|
|
83
|
+
afterSha256: string;
|
|
84
|
+
beforeContent: string | null;
|
|
85
|
+
afterContent: string;
|
|
86
|
+
}
|
|
87
|
+
export interface RepairReceiptPrecondition {
|
|
88
|
+
path: string;
|
|
89
|
+
sha256: string;
|
|
90
|
+
}
|
|
91
|
+
export declare function assertRepairTargetPath(repoPath: string, file: string): void;
|
|
92
|
+
export declare function assertRepairScope(changes: RepairFileChange[]): void;
|
|
93
|
+
export declare function verifyRepairReceipt(receipt: RepairReceipt): boolean;
|
|
94
|
+
export declare function sha256Attestation(attestation: Attestation): string;
|
|
95
|
+
export declare function composePortRepair(source: string, service: string, occupiedPort: number, replacementPort: number, containerPort: number): string;
|
|
96
|
+
export declare function packageManagerActivationCommand(packageManager: PackageManager, version: string | null): string | null;
|
|
97
|
+
export declare function prismaRepairCommand(repo: string): string;
|
|
98
|
+
export interface MigrationRepair {
|
|
99
|
+
id: string;
|
|
100
|
+
framework: "prisma" | "django" | "rails" | "knex" | "drizzle";
|
|
101
|
+
command: string;
|
|
102
|
+
source: string;
|
|
103
|
+
}
|
|
104
|
+
export declare function migrationRepairFor(repo: string, evidence: string): MigrationRepair | null;
|
|
105
|
+
export declare function registeredRemediationsFor(failureClass: FailureClass): {
|
|
106
|
+
id: string;
|
|
107
|
+
kind: RepairKind;
|
|
108
|
+
}[];
|
|
109
|
+
export declare function applyVerifiedRepair(repoPath: string, receiptFile?: string): RepairApplyResult;
|
|
110
|
+
export declare function repairRepo(repoPath: string, options: RepairOptions): Promise<RepairResult>;
|