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
- export const AGENT_CONFIGS = Object.freeze({ claude: Object.freeze({ id: "claude", managedSet: CLAUDE_MANAGED_SET, projectDir: Object.freeze({ envVar: "CLAUDE_PROJECT_DIR", fallback: "asset-root" }), marker: MANAGED_MARKER }), codex: Object.freeze({ id: "codex", managedSet: CODEX_MANAGED_SET, projectDir: Object.freeze({ envVar: null, fallback: "cwd" }), marker: "// javi-forge-managed: codex-pretooluse v1" }) });
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(executable) && hasManagedLiteral(tokens.slice(1), cwd, config, projectRoot)) return { allowed: false, ruleId: "shell.managed-config-tamper" };
884
- if ((/^(?:sed|perl)$/.test(executable) && 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" };
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": "507a57a15f1b967103bb1eefe701a74813d5a9603fa7bcc15579a215b03621e3",
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({ dryRun, refreshContext, }: DoctorOptions): import("react").FunctionComponentElement<import("../../ui/Doctor.js").DoctorProps>;
3
+ export default function DoctorController(options?: DoctorOptions): React.JSX.Element;
3
4
  //# sourceMappingURL=doctor.d.ts.map
@@ -1,7 +1,8 @@
1
- import { createElement, useEffect, useState } from "react";
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({ dryRun = false, refreshContext = false, }) {
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 refresh, q to quit")))));
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javi-forge",
3
- "version": "1.39.2",
3
+ "version": "1.39.4",
4
4
  "description": "Project scaffolding and AI-ready CI bootstrap",
5
5
  "type": "module",
6
6
  "bin": {