javi-forge 1.39.2 → 1.39.4
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.
|
@@ -11,6 +11,10 @@ export const SUPPORTED_TOOLS = Object.freeze(["Bash", "PowerShell", "Read", "Wri
|
|
|
11
11
|
export const POLICY_REGISTRY = Object.freeze({ schemaVersion: 1, policyVersion: 2, diagnosticsMaxBytes: 240 });
|
|
12
12
|
export function resolvePlatformSupport(platform = process.platform) { return platform === "linux" || platform === "win32" ? { supported: true } : { supported: false, reason: "unsupported-platform" }; }
|
|
13
13
|
const PROJECT_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
14
|
+
// The global Codex targets are anchored to the hook process's current-user home,
|
|
15
|
+
// never to an event field or a command token. This is intentionally an exact,
|
|
16
|
+
// two-file allowlist rather than a whole-home or whole-.codex policy.
|
|
17
|
+
const HOST_HOME = os.homedir();
|
|
14
18
|
// Per-agent adapter config (S0 core-extraction): every agent-specific input the guard needs
|
|
15
19
|
// (the isManaged protected-path set, the project-dir source, the managed marker) is resolved by
|
|
16
20
|
// the --agent selector instead of a baked-in literal. The pure evaluate*/utility engine stays
|
|
@@ -18,7 +22,8 @@ const PROJECT_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)),
|
|
|
18
22
|
// byte-identical; codex is defined so the map is agent-generic but wired end-to-end only in a later slice.
|
|
19
23
|
const CLAUDE_MANAGED_SET = Object.freeze({ exact: Object.freeze([".claude/settings.json", ".claude/settings.local.json", ".claude/CLAUDE.md", "CLAUDE.md", ".javi-forge/ci.yaml"]), prefixes: Object.freeze([".claude/hooks/", ".claude/agents/", ".claude/skills/"]), caseFoldExact: Object.freeze(["claude.md", ".claude/claude.md"]) });
|
|
20
24
|
const CODEX_MANAGED_SET = Object.freeze({ exact: Object.freeze([".codex/hooks.json", ".claude/settings.json", ".claude/settings.local.json", ".claude/CLAUDE.md", "CLAUDE.md", ".javi-forge/ci.yaml"]), prefixes: Object.freeze([".claude/hooks/", ".claude/agents/", ".claude/skills/"]), caseFoldExact: Object.freeze(["claude.md", ".claude/claude.md"]) });
|
|
21
|
-
|
|
25
|
+
const CODEX_GLOBAL_MANAGED_PATHS = Object.freeze([".codex/hooks.json", ".codex/config.toml"]);
|
|
26
|
+
export const AGENT_CONFIGS = Object.freeze({ claude: Object.freeze({ id: "claude", managedSet: CLAUDE_MANAGED_SET, globalManagedPaths: Object.freeze([]), projectDir: Object.freeze({ envVar: "CLAUDE_PROJECT_DIR", fallback: "asset-root" }), marker: MANAGED_MARKER }), codex: Object.freeze({ id: "codex", managedSet: CODEX_MANAGED_SET, globalManagedPaths: CODEX_GLOBAL_MANAGED_PATHS, projectDir: Object.freeze({ envVar: null, fallback: "cwd" }), marker: "// javi-forge-managed: codex-pretooluse v1" }) });
|
|
22
27
|
// Fail-closed agent selector: a missing/unknown --agent means we cannot know what to protect, so refuse.
|
|
23
28
|
function resolveAgentConfig(argv) { const arg = argv.find((value) => typeof value === "string" && value.startsWith("--agent=")); const id = arg === undefined ? undefined : arg.slice("--agent=".length); const config = id === undefined ? undefined : AGENT_CONFIGS[id]; if (!config) fail("invalid-config"); return config; }
|
|
24
29
|
// Project root per agent: the env var when set (Claude = CLAUDE_PROJECT_DIR); otherwise the per-agent
|
|
@@ -135,7 +140,8 @@ export function isSensitivePolicyKey(key, platform = process.platform) {
|
|
|
135
140
|
if (SENSITIVE_DIRECTORY_SUFFIXES.some((directory) => key.endsWith(directory) || key.includes(`${directory}/`))) return true;
|
|
136
141
|
return platform === "win32" ? basename.toLowerCase() === "serviceaccountkey.json" : basename === "serviceAccountKey.json";
|
|
137
142
|
}
|
|
138
|
-
function isManaged(key, managedSet = CLAUDE_MANAGED_SET, projectRoot = PROJECT_ROOT) {
|
|
143
|
+
function isManaged(key, managedSet = CLAUDE_MANAGED_SET, projectRoot = PROJECT_ROOT, globalManagedPaths = []) {
|
|
144
|
+
if (globalManagedPaths.some((relative) => key === canonicalizePolicyPath(path.join(HOST_HOME, relative)))) return true;
|
|
139
145
|
const project = canonicalizePolicyPath(projectRoot);
|
|
140
146
|
if (!key.startsWith(`${project}/`) && key !== project) return false;
|
|
141
147
|
const relative = key.slice(project.length + 1);
|
|
@@ -148,7 +154,7 @@ function isManaged(key, managedSet = CLAUDE_MANAGED_SET, projectRoot = PROJECT_R
|
|
|
148
154
|
function evaluateFile(toolName, filePath, config = AGENT_CONFIGS.claude, projectRoot = PROJECT_ROOT) {
|
|
149
155
|
const keys = policyPathKeys(filePath);
|
|
150
156
|
if (keys.some((key) => isSensitivePolicyKey(key))) return { allowed: false, ruleId: "path.sensitive" };
|
|
151
|
-
if (toolName !== "Read" && keys.some((key) => isManaged(key, config.managedSet, projectRoot))) return { allowed: false, ruleId: "path.managed-config" };
|
|
157
|
+
if (toolName !== "Read" && keys.some((key) => isManaged(key, config.managedSet, projectRoot, config.globalManagedPaths))) return { allowed: false, ruleId: "path.managed-config" };
|
|
152
158
|
return { allowed: true };
|
|
153
159
|
}
|
|
154
160
|
function lex(command, powershell = false) {
|
|
@@ -753,7 +759,7 @@ function hasManagedLiteral(tokens, cwd, config = AGENT_CONFIGS.claude, projectRo
|
|
|
753
759
|
return tokens.some((token) => {
|
|
754
760
|
if ((token.startsWith("-") && !/^-(?:LiteralPath|Path):/i.test(token)) || !/[\\/.]/.test(token)) return false;
|
|
755
761
|
try {
|
|
756
|
-
return isManaged(canonicalizePolicyPath(token.replace(/^(?:-LiteralPath:|-Path:)/i, "").replace(/[;,]$/, ""), { base: cwd, projectRoot }), config.managedSet, projectRoot);
|
|
762
|
+
return isManaged(canonicalizePolicyPath(token.replace(/^(?:-LiteralPath:|-Path:)/i, "").replace(/[;,]$/, ""), { base: cwd, projectRoot }), config.managedSet, projectRoot, config.globalManagedPaths);
|
|
757
763
|
} catch {
|
|
758
764
|
return false;
|
|
759
765
|
}
|
|
@@ -857,6 +863,7 @@ function evaluateBash(command, cwd, config = AGENT_CONFIGS.claude, projectRoot =
|
|
|
857
863
|
if (reduced.ambiguity) return ambiguityDecision(reduced.ambiguity);
|
|
858
864
|
const tokens = reduced.tokens;
|
|
859
865
|
const executable = (tokens[0] ?? "").toLowerCase();
|
|
866
|
+
const executableName = path.basename(executable.replaceAll("\\", "/"));
|
|
860
867
|
const identity = normalizeLiteralUtilityIdentity(tokens[0] ?? "");
|
|
861
868
|
const rmOptions = tokens.filter((token) => token.startsWith("-")).join("");
|
|
862
869
|
if ((executable === "rm" && /r/i.test(rmOptions) && /f/i.test(rmOptions) && tokens.some(isCriticalTarget)) || /^mkfs/.test(executable) || (executable === "dd" && tokens.some((token) => /^of=\/dev\/(?:sd|nvme|vd|disk)/.test(token)))) return { allowed: false, ruleId: "shell.destructive-root" };
|
|
@@ -880,8 +887,8 @@ function evaluateBash(command, cwd, config = AGENT_CONFIGS.claude, projectRoot =
|
|
|
880
887
|
if (["cat", "less", "more", "head", "tail", "bat", "grep", "rg", "sed", "awk", "source", ".", "cp", "install"].includes(executable) && hasSensitiveLiteral(tokens.slice(1), cwd, projectRoot)) return { allowed: false, ruleId: "shell.sensitive-read" };
|
|
881
888
|
if (tokens.some((token) => token === "<") && hasSensitiveLiteral(tokens, cwd, projectRoot)) return { allowed: false, ruleId: "shell.sensitive-read" };
|
|
882
889
|
if (executable === "git" && tokens[1]?.toLowerCase() === "push" && tokens.some((token) => ["-f", "--force", "--force-with-lease"].includes(token.toLowerCase()))) return { allowed: false, ruleId: "shell.force-push" };
|
|
883
|
-
if (["rm", "mv", "cp", "install", "truncate", "touch", "chmod", "chown", "tee"].includes(
|
|
884
|
-
if ((/^(?:sed|perl)$/.test(
|
|
890
|
+
if (["rm", "mv", "cp", "install", "truncate", "touch", "chmod", "chown", "tee"].includes(executableName) && hasManagedLiteral(tokens.slice(1), cwd, config, projectRoot)) return { allowed: false, ruleId: "shell.managed-config-tamper" };
|
|
891
|
+
if ((/^(?:sed|perl)$/.test(executableName) && tokens.some((token) => token.startsWith("-i")) && hasManagedLiteral(tokens, cwd, config, projectRoot)) || (tokens.includes(">") && hasManagedLiteral(tokens, cwd, config, projectRoot))) return { allowed: false, ruleId: "shell.managed-config-tamper" };
|
|
885
892
|
if (/^(?:powershell|pwsh)(?:\.exe)?$/i.test(executable) && tokens.some((token) => /^-(?:enc|encodedcommand)$/i.test(token))) return { allowed: false, ruleId: "shell.obfuscated-interpreter" };
|
|
886
893
|
if (/^(?:bash|sh|zsh|dash|ksh)$/.test(executable)) {
|
|
887
894
|
const flag = tokens.findIndex((token) => /^-[^-]*c[^-]*$/.test(token));
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"name": "javi-forge-skillguard-pre-tool-use.mjs",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"policyVersion": 2,
|
|
7
|
-
"sha256": "
|
|
7
|
+
"sha256": "f55ece1aea76251ec005f2b731fa44a58b6245fef8150b58486ae062167a2377",
|
|
8
8
|
"historical": [
|
|
9
9
|
"78be7e6613c012280b7ad17886462ba166b63ebd031e34565d757b3a0796d7cc",
|
|
10
10
|
"5dc2a5c31131f4ac7d8657c78b950de52776aad6eaefe78ea0d764a9963c4425",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"54a270f28b068450b79547a88ec6f2d4854514392fd5f38ed1d6174ea093d7aa",
|
|
14
14
|
"9a565cec31d9e091e3fb9420b86685f824733bc1ebe479f086b2b955aba6ef3e",
|
|
15
15
|
"59fc4224975ad64cfc85bab50ec60d9bd4948070e9d41f42ab43e6d8231c19a1",
|
|
16
|
-
"6edfbb31ce0551b27e38ae1ffd1daf9cc4bea54f2c86687c5124132af5b8c0af"
|
|
16
|
+
"6edfbb31ce0551b27e38ae1ffd1daf9cc4bea54f2c86687c5124132af5b8c0af",
|
|
17
|
+
"507a57a15f1b967103bb1eefe701a74813d5a9603fa7bcc15579a215b03621e3"
|
|
17
18
|
]
|
|
18
19
|
},
|
|
19
20
|
"settingsEntries": {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React from "react";
|
|
1
2
|
import { type DoctorOptions } from "../../commands/doctor.js";
|
|
2
|
-
export default function DoctorController(
|
|
3
|
+
export default function DoctorController(options?: DoctorOptions): React.JSX.Element;
|
|
3
4
|
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
2
|
import { runDoctor } from "../../commands/doctor.js";
|
|
3
3
|
import Doctor from "../../ui/Doctor.js";
|
|
4
|
-
export default function DoctorController(
|
|
4
|
+
export default function DoctorController(options = {}) {
|
|
5
|
+
const { dryRun = false, refreshContext = false } = options;
|
|
5
6
|
const [result, setResult] = useState(null);
|
|
6
7
|
const [error, setError] = useState(null);
|
|
7
8
|
const [loading, setLoading] = useState(true);
|
|
@@ -22,6 +23,6 @@ export default function DoctorController({ dryRun = false, refreshContext = fals
|
|
|
22
23
|
useEffect(() => {
|
|
23
24
|
runCheck();
|
|
24
25
|
}, [dryRun, refreshContext]);
|
|
25
|
-
return createElement(Doctor, { loading, result, error, onRerun: runCheck });
|
|
26
|
+
return (React.createElement(Doctor, { loading: loading, result: result, error: error, onRerun: runCheck }));
|
|
26
27
|
}
|
|
27
28
|
//# sourceMappingURL=doctor.js.map
|
package/dist/ui/Doctor.js
CHANGED
|
@@ -82,6 +82,6 @@ export default function Doctor({ loading, result, error, onRerun, }) {
|
|
|
82
82
|
check.detail)) : null))))));
|
|
83
83
|
}))),
|
|
84
84
|
!loading && (React.createElement(Box, { marginTop: 1 },
|
|
85
|
-
React.createElement(Text, { color: theme.muted, dimColor: true }, "Press r to
|
|
85
|
+
React.createElement(Text, { color: theme.muted, dimColor: true }, "Press r to rerun checks, q to quit")))));
|
|
86
86
|
}
|
|
87
87
|
//# sourceMappingURL=Doctor.js.map
|