javi-forge 1.34.1 → 1.35.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.
@@ -87,13 +87,31 @@ function policyPathKeys(input, options = {}) {
87
87
  return real === lexical ? [lexical] : [lexical, real];
88
88
  }
89
89
  function isAbsolutePolicyPath(value) { return POSIX_ABSOLUTE.test(value) || WINDOWS_DRIVE.test(value) || WINDOWS_UNC.test(value) || /^\\(?:\\\?|\?\?|\\\.)\\/i.test(value); }
90
+ // Home-relative credential files, matched as a path SUFFIX (never an expanded
91
+ // literal home) so one rule holds for every user, container and HOME. Literals
92
+ // stay lowercase because lexicalNormalize folds keys on darwin/win32.
93
+ const SENSITIVE_PATH_SUFFIXES = Object.freeze(["/.aws/credentials", "/.kube/config", "/.config/gcloud/application_default_credentials.json", "/.docker/config.json", "/.config/gh/hosts.yml"]);
94
+ // Absolute system secrets: the shadow suite holds every local account's password
95
+ // hash, and passwd/vipw leave the "-" backup beside it holding the same secret.
96
+ const SENSITIVE_ABSOLUTE_KEYS = Object.freeze(["/etc/shadow", "/etc/shadow-", "/etc/gshadow", "/etc/gshadow-"]);
97
+ // /proc/<pid>/environ is the FULL environment - every secret - of ANY process the
98
+ // agent can see, so it is a first-class exfiltration sink. The corpus idiom is
99
+ // literal matching; this is the one place a wildcard is unavoidable because the
100
+ // pid component is unbounded. Kept narrow (environ leaves only) and fail-closed:
101
+ // it covers the self/thread-self aliases and the per-thread task/<tid> form.
102
+ const PROC_ENVIRON_KEY = /^\/proc\/(?:\d+|self|thread-self)\/(?:task\/\d+\/)?environ$/;
103
+ // Secret Service / GNOME keyring store: every file under it is credential
104
+ // material, so the directory itself and its whole subtree are sensitive.
105
+ const SENSITIVE_DIRECTORY_SUFFIXES = Object.freeze(["/.local/share/keyrings"]);
90
106
  export function isSensitivePolicyKey(key, platform = process.platform) {
91
107
  const parts = key.split("/").filter(Boolean);
92
108
  const basename = parts.at(-1) ?? "";
93
109
  if (/^\.env(?:\..+)?$/i.test(basename) && !/^\.env\.(?:example|sample|template)$/i.test(basename)) return true;
94
110
  if ([".npmrc", ".pypirc", ".netrc", ".git-credentials"].includes(basename.toLowerCase())) return true;
95
111
  if (parts.some((part) => part === ".ssh" || part === ".gnupg")) return true;
96
- if (key.endsWith("/.aws/credentials") || key.endsWith("/.kube/config") || key.endsWith("/.config/gcloud/application_default_credentials.json")) return true;
112
+ if (SENSITIVE_ABSOLUTE_KEYS.includes(key) || PROC_ENVIRON_KEY.test(key)) return true;
113
+ if (SENSITIVE_PATH_SUFFIXES.some((suffix) => key.endsWith(suffix))) return true;
114
+ if (SENSITIVE_DIRECTORY_SUFFIXES.some((directory) => key.endsWith(directory) || key.includes(`${directory}/`))) return true;
97
115
  return platform === "win32" || platform === "darwin" ? basename.toLowerCase() === "serviceaccountkey.json" : basename === "serviceAccountKey.json";
98
116
  }
99
117
  function isManaged(key) {
@@ -284,7 +302,12 @@ function classifyLongOption(token, longOptions) {
284
302
  }
285
303
  const ASSIGNMENT_TOKEN = /^[A-Za-z_][A-Za-z0-9_]*=/;
286
304
  const SHORT_BUNDLE = /^-[^-]/;
287
- const CRITICAL_TARGET_CORPUS = Object.freeze(["/", "/*", "~", "$HOME", "${HOME}", ".", "..", PROJECT_ROOT]);
305
+ // FHS roots whose wholesale destruction bricks the host. CRITICAL_TARGET gates
306
+ // ONLY destructive writes (rm -rf, recursive/777 chmod) and never reads, and
307
+ // membership is exact-token, so `rm -rf /var/tmp/scratch` stays allowed: only the
308
+ // root token itself is protected, in the same bare / trailing / glob forms as "/".
309
+ const CRITICAL_SYSTEM_ROOTS = ["/etc", "/usr", "/var", "/boot"];
310
+ const CRITICAL_TARGET_CORPUS = Object.freeze(["/", "/*", "~", "$HOME", "${HOME}", ".", "..", PROJECT_ROOT, ...CRITICAL_SYSTEM_ROOTS.flatMap((root) => [root, `${root}/`, `${root}/*`])]);
288
311
  const isCriticalTarget = (token) => CRITICAL_TARGET_CORPUS.includes(token);
289
312
  const OCTAL_MODE_SHAPE = /^0?[0-7]{3,4}$/;
290
313
  const SYMBOLIC_MODE_SHAPE = /^[ugoa]*[+=-][rwxXstugo]+(?:,[ugoa]*[+=-][rwxXstugo]+)*$/;
@@ -1 +1 @@
1
- {"schemaVersion":1,"asset":{"name":"javi-forge-skillguard-pre-tool-use.mjs","version":1,"policyVersion":1,"sha256":"78be7e6613c012280b7ad17886462ba166b63ebd031e34565d757b3a0796d7cc","historical":[]},"settingsEntries":{"current":{"version":1,"canonicalSha256":"038c59a91bf8967f6908afed74c465f1e7030254e11e4f8738975d6d708424d4"},"historical":[]},"installerHelpers":{"windowsSecureObject":{"name":"javi-forge-windows-secure-object.ps1","sha256":"2289ef6ac6b039ec74dc3ea0894413e243ff9bea963f04008a356b3838f9b8dd"}}}
1
+ {"schemaVersion":1,"asset":{"name":"javi-forge-skillguard-pre-tool-use.mjs","version":1,"policyVersion":1,"sha256":"5dc2a5c31131f4ac7d8657c78b950de52776aad6eaefe78ea0d764a9963c4425","historical":["78be7e6613c012280b7ad17886462ba166b63ebd031e34565d757b3a0796d7cc"]},"settingsEntries":{"current":{"version":1,"canonicalSha256":"038c59a91bf8967f6908afed74c465f1e7030254e11e4f8738975d6d708424d4"},"historical":[]},"installerHelpers":{"windowsSecureObject":{"name":"javi-forge-windows-secure-object.ps1","sha256":"2289ef6ac6b039ec74dc3ea0894413e243ff9bea963f04008a356b3838f9b8dd"}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javi-forge",
3
- "version": "1.34.1",
3
+ "version": "1.35.0",
4
4
  "description": "Project scaffolding and AI-ready CI bootstrap",
5
5
  "type": "module",
6
6
  "bin": {