@solongate/proxy 0.83.5 → 0.83.6

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/hooks/audit.mjs CHANGED
@@ -11,7 +11,7 @@ import { homedir } from 'node:os';
11
11
  // Bump on every audit hook change. The cloud serves the newest version; the guard
12
12
  // hook installs it on its next run (no re-login needed). See guard.mjs
13
13
  // fetchAndInstallHook / maybeSelfUpdate.
14
- const HOOK_VERSION = 23;
14
+ const HOOK_VERSION = 24;
15
15
 
16
16
  function loadEnvKey(dir) {
17
17
  try {
@@ -375,9 +375,12 @@ function loadLocalLogs() {
375
375
  const c = JSON.parse(readFileSync(f, 'utf-8'));
376
376
  const l = c && c.security && c.security.localLogs;
377
377
  if (l && l.enabled && typeof l.path === 'string' && l.path.trim()) return { path: l.path.trim() };
378
- // A cache that HAS a security block and no localLogs means the project
379
- // really turned local logging off cloud it is.
380
- if (c && c.security) return null;
378
+ // A cache that carries the key at all has an ANSWER, and the answer is
379
+ // "not local" including when it is null, which is what the refresh
380
+ // writes for a project with no security config. Requiring it to be
381
+ // truthy sent that case to the device-wide marker instead, so a stale
382
+ // marker kept ALLOW rows on disk after local logging was switched off.
383
+ if (c && 'security' in c) return null;
381
384
  }
382
385
  } catch { /* unreadable cache — fall through to the marker */ }
383
386
  // No usable config (cold cache, or a refresh that failed while the key was
@@ -6536,10 +6536,10 @@ import { resolve, join, dirname, isAbsolute } from "node:path";
6536
6536
  import { homedir } from "node:os";
6537
6537
  import { gunzipSync } from "node:zlib";
6538
6538
  import { createHash } from "node:crypto";
6539
- var HOOK_VERSION = 71;
6539
+ var HOOK_VERSION = 72;
6540
6540
  function localLogsOnly(security) {
6541
- if (security && typeof security === "object") {
6542
- const l = security.localLogs;
6541
+ if (security !== void 0) {
6542
+ const l = security && security.localLogs;
6543
6543
  return !!(l && l.enabled && typeof l.path === "string" && l.path.trim());
6544
6544
  }
6545
6545
  try {
@@ -6813,8 +6813,7 @@ async function refreshPolicyCache() {
6813
6813
  const body = await res.json();
6814
6814
  if (typeof body?.self_protection_enabled === "boolean")
6815
6815
  selfProtect = body.self_protection_enabled;
6816
- if (body?.security !== void 0)
6817
- security = body.security;
6816
+ security = body?.security !== void 0 ? body.security : null;
6818
6817
  if (body?.hook_versions && typeof body.hook_versions === "object")
6819
6818
  hookVersions = body.hook_versions;
6820
6819
  policy = body && body.policy ? body.policy : null;
package/hooks/guard.mjs CHANGED
@@ -32,20 +32,28 @@ import { createHash } from 'node:crypto';
32
32
  // the installed hook self-updates when the cloud version is higher (see
33
33
  // maybeSelfUpdate). This is what makes guard fixes propagate without a manual
34
34
  // reinstall — the same trust model as the OPA WASM this hook already runs.
35
- const HOOK_VERSION = 71;
35
+ const HOOK_VERSION = 72;
36
36
 
37
37
  // True when local log storage is ON. In that mode logs are kept LOCAL ONLY and
38
38
  // nothing is sent to the cloud audit log.
39
+ //
40
+ // `undefined` is the ONLY value that means "we do not know yet". `null` is an
41
+ // answer: it is what the refresh writes when the API returns no security block,
42
+ // i.e. this project has no local-log config, i.e. cloud. Treating null as
43
+ // unknown is what kept logs on the machine after local logging was switched
44
+ // off — the answer fell through to the device-wide marker, and that marker is
45
+ // shared by every agent on the box while the policy cache is per agent, so
46
+ // whichever one refreshed last decided where everybody's logs went.
39
47
  function localLogsOnly(security) {
40
- if (security && typeof security === 'object') {
41
- const l = security.localLogs;
48
+ if (security !== undefined) {
49
+ const l = security && security.localLogs;
42
50
  return !!(l && l.enabled && typeof l.path === 'string' && l.path.trim());
43
51
  }
44
- // security UNKNOWN (cold start / policy-cache miss / mid-toggle): consult the
45
- // persisted local-mode marker so a DENY is NEVER leaked to the cloud when this
46
- // device is in local-only mode. Without this fallback the first call(s) before
47
- // the policy cache warms POST the denial to the cloud and fire webhooks/alerts
48
- // even though the user chose local-only.
52
+ // Genuinely unknown (cold start, unreadable cache): consult the persisted
53
+ // marker so a DENY is NEVER leaked to the cloud when this device is in
54
+ // local-only mode. Without this fallback the first call(s) before the policy
55
+ // cache warms POST the denial to the cloud and fire webhooks/alerts even
56
+ // though the user chose local-only.
49
57
  try {
50
58
  const m = JSON.parse(readFileSync(join(resolve(homedir(), '.solongate'), '.local-logs-mode.json'), 'utf-8'));
51
59
  return !!(m && m.localOnly);
@@ -421,7 +429,12 @@ async function refreshPolicyCache() {
421
429
  if (res.ok) {
422
430
  const body = await res.json();
423
431
  if (typeof body?.self_protection_enabled === 'boolean') selfProtect = body.self_protection_enabled;
424
- if (body?.security !== undefined) security = body.security;
432
+ // A successful answer REPLACES the security block rather than merging
433
+ // into it. Only overwriting when the field was present meant a config
434
+ // the API had stopped sending lived on in the cache forever — switch
435
+ // local logging off in the dashboard and this device kept writing to
436
+ // disk and kept skipping the cloud, with nothing to show why.
437
+ security = body?.security !== undefined ? body.security : null;
425
438
  if (body?.hook_versions && typeof body.hook_versions === 'object') hookVersions = body.hook_versions;
426
439
  policy = (body && body.policy) ? body.policy : null;
427
440
  noteAuthResult(true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.83.5",
3
+ "version": "0.83.6",
4
4
  "description": "AI tool security proxy: protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. No code changes required.",
5
5
  "type": "module",
6
6
  "bin": {