@geml/geml 1.3.2 → 1.4.2
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/LICENSE +21 -21
- package/README.md +155 -109
- package/codemap/adapters/crg.mjs +120 -120
- package/codemap/adapters/joern.mjs +131 -131
- package/codemap/adapters/scip.mjs +658 -658
- package/codemap/browser-stub.mjs +29 -29
- package/codemap/build.mjs +609 -579
- package/codemap/cross-stack.mjs +303 -0
- package/codemap/detect.mjs +399 -399
- package/codemap/emit.mjs +480 -432
- package/codemap/entries.mjs +129 -129
- package/codemap/exclude.mjs +52 -52
- package/codemap/find.mjs +63 -63
- package/codemap/foldings.mjs +110 -110
- package/codemap/joern-export.sc +83 -83
- package/codemap/mcp-server.mjs +172 -172
- package/codemap/normalize.mjs +275 -272
- package/codemap/recipe-trust.mjs +103 -103
- package/codemap/refresh.mjs +310 -310
- package/codemap/render-all.mjs +64 -64
- package/codemap/serve.mjs +578 -578
- package/codemap/sfc-virtualize.mjs +367 -367
- package/codemap/verify.mjs +148 -143
- package/dist/block-edit.d.ts +1 -0
- package/dist/block-edit.js +112 -0
- package/dist/geml.js +759 -161
- package/dist/render.d.ts +2 -2
- package/dist/render.js +261 -178
- package/package.json +1 -2
package/codemap/recipe-trust.mjs
CHANGED
|
@@ -1,103 +1,103 @@
|
|
|
1
|
-
// Shared TRUST GATE for codemap recipes (security fix C2 — RCE).
|
|
2
|
-
//
|
|
3
|
-
// A codemap's _index/refresh.json is COMMITTED DATA whose `steps[]` are run
|
|
4
|
-
// through a shell by `geml codemap refresh` (spawnSync(step,{shell:true})).
|
|
5
|
-
// Cloning a hostile repo and running `geml codemap refresh` — which the
|
|
6
|
-
// geml-code-graph skill, `serve --watch`, and a PostToolUse hook all trigger —
|
|
7
|
-
// would otherwise execute arbitrary commands. The old "up-to-date" guard is
|
|
8
|
-
// bypassable and does not gate execution.
|
|
9
|
-
//
|
|
10
|
-
// The fix content-addresses each recipe (a stable fingerprint of its steps)
|
|
11
|
-
// and records which fingerprints the user has EXPLICITLY approved in a store
|
|
12
|
-
// kept OUTSIDE any repo (so a repo can never pre-approve itself). refresh
|
|
13
|
-
// refuses to execute a recipe whose fingerprint is not in the store; build
|
|
14
|
-
// auto-trusts the recipe it just authored (the user ran it locally).
|
|
15
|
-
//
|
|
16
|
-
// Trust gates WHO may run a recipe. Security fix R2-1 additionally changed HOW
|
|
17
|
-
// steps are stored: a step is now a STRUCTURED object { cwd?, env?, argv:[...] }
|
|
18
|
-
// so attacker-controllable paths are never interpolated into a shell string at
|
|
19
|
-
// rest, and refresh executes them without an attacker-influenced command line.
|
|
20
|
-
// The fingerprint below canonicalizes that structured form.
|
|
21
|
-
import { createHash } from "node:crypto";
|
|
22
|
-
import { homedir } from "node:os";
|
|
23
|
-
import { join, dirname } from "node:path";
|
|
24
|
-
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
25
|
-
|
|
26
|
-
export const RECIPE_VERSION = 1; // on-disk step schema; bump ONLY on a real format change
|
|
27
|
-
|
|
28
|
-
// Canonicalize ONE recipe step for fingerprinting. Since security fix R2-1 a
|
|
29
|
-
// step is a STRUCTURED object { cwd?, env?, argv:[...] } (no shell string is
|
|
30
|
-
// ever stored). We emit a FIXED key order (cwd, env, argv), env keys SORTED so
|
|
31
|
-
// the fingerprint is independent of how the env map was built, and every value
|
|
32
|
-
// coerced to a string. Anything that is NOT a structured step (a legacy
|
|
33
|
-
// pre-R2-1 shell string, or a malformed entry) coerces to its String() form, so
|
|
34
|
-
// pre-existing string recipes keep the EXACT fingerprint they had before this
|
|
35
|
-
// change (backward compatible), while structured recipes get a stable identity.
|
|
36
|
-
function canonicalStep(step) {
|
|
37
|
-
if (step && typeof step === "object" && Array.isArray(step.argv)) {
|
|
38
|
-
const out = {};
|
|
39
|
-
if (step.cwd != null) out.cwd = String(step.cwd);
|
|
40
|
-
if (step.env && typeof step.env === "object") {
|
|
41
|
-
const env = {};
|
|
42
|
-
for (const k of Object.keys(step.env).sort()) env[k] = String(step.env[k]);
|
|
43
|
-
out.env = env;
|
|
44
|
-
}
|
|
45
|
-
out.argv = step.argv.map((a) => String(a));
|
|
46
|
-
return out;
|
|
47
|
-
}
|
|
48
|
-
return String(step);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Fingerprint = sha256 over a canonical JSON of {root, steps}. build (when it
|
|
52
|
-
// records refresh.json) and refresh (when it is about to run it) both call
|
|
53
|
-
// this on the same parsed recipe object, so they agree exactly. `root` is
|
|
54
|
-
// included because it is the base dir every step runs under — the same steps
|
|
55
|
-
// under a different root are a different execution and deserve a different
|
|
56
|
-
// identity. Deterministic: fixed key order, canonicalized steps, no timestamps.
|
|
57
|
-
export function recipeFingerprint(recipe) {
|
|
58
|
-
const steps = Array.isArray(recipe?.steps) ? recipe.steps.map(canonicalStep) : [];
|
|
59
|
-
const root = recipe?.root == null ? "" : String(recipe.root);
|
|
60
|
-
const canonical = JSON.stringify({ root, steps });
|
|
61
|
-
return createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Where the trust store lives — NEVER inside a repo. GEML_TRUST_STORE is an
|
|
65
|
-
// explicit override (test isolation / unusual homes). Otherwise it sits under
|
|
66
|
-
// the XDG config dir, falling back to ~/.config/geml, which is a sane
|
|
67
|
-
// cross-platform home (on Windows homedir() is C:\Users\<name>).
|
|
68
|
-
export function trustStorePath() {
|
|
69
|
-
if (process.env.GEML_TRUST_STORE) return process.env.GEML_TRUST_STORE;
|
|
70
|
-
const cfgHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
71
|
-
return join(cfgHome, "geml", "trusted-recipes.json");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Read the store DEFENSIVELY: a missing, unreadable, or malformed store means
|
|
75
|
-
// "nothing is trusted". A broken store must never silently trust a recipe.
|
|
76
|
-
export function readTrustStore() {
|
|
77
|
-
try {
|
|
78
|
-
const obj = JSON.parse(readFileSync(trustStorePath(), "utf8"));
|
|
79
|
-
if (obj && typeof obj === "object" && obj.recipes && typeof obj.recipes === "object") {
|
|
80
|
-
return { version: obj.version || 1, recipes: obj.recipes };
|
|
81
|
-
}
|
|
82
|
-
} catch { /* missing / unreadable / malformed: treat as empty */ }
|
|
83
|
-
return { version: 1, recipes: {} };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// True only when this exact recipe fingerprint has been approved.
|
|
87
|
-
export function isRecipeTrusted(fingerprint) {
|
|
88
|
-
const store = readTrustStore();
|
|
89
|
-
return Object.prototype.hasOwnProperty.call(store.recipes, fingerprint);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Record a fingerprint as trusted, MERGING into any existing store (never
|
|
93
|
-
// clobbering other approvals). Creates parent dirs. Returns the store path.
|
|
94
|
-
// THROWS on write failure: a caller that meant to trust must learn it did NOT,
|
|
95
|
-
// rather than proceed on the false belief that the recipe is now safe.
|
|
96
|
-
export function trustRecipe(fingerprint, graphDir) {
|
|
97
|
-
const store = readTrustStore();
|
|
98
|
-
store.recipes[fingerprint] = { graphDir: graphDir ? String(graphDir) : undefined, addedAt: Date.now() };
|
|
99
|
-
const p = trustStorePath();
|
|
100
|
-
mkdirSync(dirname(p), { recursive: true });
|
|
101
|
-
writeFileSync(p, JSON.stringify(store, null, 2) + "\n");
|
|
102
|
-
return p;
|
|
103
|
-
}
|
|
1
|
+
// Shared TRUST GATE for codemap recipes (security fix C2 — RCE).
|
|
2
|
+
//
|
|
3
|
+
// A codemap's _index/refresh.json is COMMITTED DATA whose `steps[]` are run
|
|
4
|
+
// through a shell by `geml codemap refresh` (spawnSync(step,{shell:true})).
|
|
5
|
+
// Cloning a hostile repo and running `geml codemap refresh` — which the
|
|
6
|
+
// geml-code-graph skill, `serve --watch`, and a PostToolUse hook all trigger —
|
|
7
|
+
// would otherwise execute arbitrary commands. The old "up-to-date" guard is
|
|
8
|
+
// bypassable and does not gate execution.
|
|
9
|
+
//
|
|
10
|
+
// The fix content-addresses each recipe (a stable fingerprint of its steps)
|
|
11
|
+
// and records which fingerprints the user has EXPLICITLY approved in a store
|
|
12
|
+
// kept OUTSIDE any repo (so a repo can never pre-approve itself). refresh
|
|
13
|
+
// refuses to execute a recipe whose fingerprint is not in the store; build
|
|
14
|
+
// auto-trusts the recipe it just authored (the user ran it locally).
|
|
15
|
+
//
|
|
16
|
+
// Trust gates WHO may run a recipe. Security fix R2-1 additionally changed HOW
|
|
17
|
+
// steps are stored: a step is now a STRUCTURED object { cwd?, env?, argv:[...] }
|
|
18
|
+
// so attacker-controllable paths are never interpolated into a shell string at
|
|
19
|
+
// rest, and refresh executes them without an attacker-influenced command line.
|
|
20
|
+
// The fingerprint below canonicalizes that structured form.
|
|
21
|
+
import { createHash } from "node:crypto";
|
|
22
|
+
import { homedir } from "node:os";
|
|
23
|
+
import { join, dirname } from "node:path";
|
|
24
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
25
|
+
|
|
26
|
+
export const RECIPE_VERSION = 1; // on-disk step schema; bump ONLY on a real format change
|
|
27
|
+
|
|
28
|
+
// Canonicalize ONE recipe step for fingerprinting. Since security fix R2-1 a
|
|
29
|
+
// step is a STRUCTURED object { cwd?, env?, argv:[...] } (no shell string is
|
|
30
|
+
// ever stored). We emit a FIXED key order (cwd, env, argv), env keys SORTED so
|
|
31
|
+
// the fingerprint is independent of how the env map was built, and every value
|
|
32
|
+
// coerced to a string. Anything that is NOT a structured step (a legacy
|
|
33
|
+
// pre-R2-1 shell string, or a malformed entry) coerces to its String() form, so
|
|
34
|
+
// pre-existing string recipes keep the EXACT fingerprint they had before this
|
|
35
|
+
// change (backward compatible), while structured recipes get a stable identity.
|
|
36
|
+
function canonicalStep(step) {
|
|
37
|
+
if (step && typeof step === "object" && Array.isArray(step.argv)) {
|
|
38
|
+
const out = {};
|
|
39
|
+
if (step.cwd != null) out.cwd = String(step.cwd);
|
|
40
|
+
if (step.env && typeof step.env === "object") {
|
|
41
|
+
const env = {};
|
|
42
|
+
for (const k of Object.keys(step.env).sort()) env[k] = String(step.env[k]);
|
|
43
|
+
out.env = env;
|
|
44
|
+
}
|
|
45
|
+
out.argv = step.argv.map((a) => String(a));
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
return String(step);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Fingerprint = sha256 over a canonical JSON of {root, steps}. build (when it
|
|
52
|
+
// records refresh.json) and refresh (when it is about to run it) both call
|
|
53
|
+
// this on the same parsed recipe object, so they agree exactly. `root` is
|
|
54
|
+
// included because it is the base dir every step runs under — the same steps
|
|
55
|
+
// under a different root are a different execution and deserve a different
|
|
56
|
+
// identity. Deterministic: fixed key order, canonicalized steps, no timestamps.
|
|
57
|
+
export function recipeFingerprint(recipe) {
|
|
58
|
+
const steps = Array.isArray(recipe?.steps) ? recipe.steps.map(canonicalStep) : [];
|
|
59
|
+
const root = recipe?.root == null ? "" : String(recipe.root);
|
|
60
|
+
const canonical = JSON.stringify({ root, steps });
|
|
61
|
+
return createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Where the trust store lives — NEVER inside a repo. GEML_TRUST_STORE is an
|
|
65
|
+
// explicit override (test isolation / unusual homes). Otherwise it sits under
|
|
66
|
+
// the XDG config dir, falling back to ~/.config/geml, which is a sane
|
|
67
|
+
// cross-platform home (on Windows homedir() is C:\Users\<name>).
|
|
68
|
+
export function trustStorePath() {
|
|
69
|
+
if (process.env.GEML_TRUST_STORE) return process.env.GEML_TRUST_STORE;
|
|
70
|
+
const cfgHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
71
|
+
return join(cfgHome, "geml", "trusted-recipes.json");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Read the store DEFENSIVELY: a missing, unreadable, or malformed store means
|
|
75
|
+
// "nothing is trusted". A broken store must never silently trust a recipe.
|
|
76
|
+
export function readTrustStore() {
|
|
77
|
+
try {
|
|
78
|
+
const obj = JSON.parse(readFileSync(trustStorePath(), "utf8"));
|
|
79
|
+
if (obj && typeof obj === "object" && obj.recipes && typeof obj.recipes === "object") {
|
|
80
|
+
return { version: obj.version || 1, recipes: obj.recipes };
|
|
81
|
+
}
|
|
82
|
+
} catch { /* missing / unreadable / malformed: treat as empty */ }
|
|
83
|
+
return { version: 1, recipes: {} };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// True only when this exact recipe fingerprint has been approved.
|
|
87
|
+
export function isRecipeTrusted(fingerprint) {
|
|
88
|
+
const store = readTrustStore();
|
|
89
|
+
return Object.prototype.hasOwnProperty.call(store.recipes, fingerprint);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Record a fingerprint as trusted, MERGING into any existing store (never
|
|
93
|
+
// clobbering other approvals). Creates parent dirs. Returns the store path.
|
|
94
|
+
// THROWS on write failure: a caller that meant to trust must learn it did NOT,
|
|
95
|
+
// rather than proceed on the false belief that the recipe is now safe.
|
|
96
|
+
export function trustRecipe(fingerprint, graphDir) {
|
|
97
|
+
const store = readTrustStore();
|
|
98
|
+
store.recipes[fingerprint] = { graphDir: graphDir ? String(graphDir) : undefined, addedAt: Date.now() };
|
|
99
|
+
const p = trustStorePath();
|
|
100
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
101
|
+
writeFileSync(p, JSON.stringify(store, null, 2) + "\n");
|
|
102
|
+
return p;
|
|
103
|
+
}
|