@solongate/proxy 0.57.0 → 0.59.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.
package/dist/login.js CHANGED
@@ -141,19 +141,6 @@ var SHIM_END = "# <<< SolonGate shield <<<";
141
141
  function escapeRe(s) {
142
142
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
143
143
  }
144
- function resolveRealClaude() {
145
- try {
146
- const finder = process.platform === "win32" ? "where" : "which";
147
- const out = execFileSync(finder, ["claude"], { encoding: "utf-8" }).split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
148
- if (process.platform === "win32") {
149
- const low = (s) => s.toLowerCase();
150
- return out.find((l) => low(l).endsWith(".cmd")) || out.find((l) => low(l).endsWith(".exe")) || out.find((l) => low(l).endsWith(".bat")) || out[0] || null;
151
- }
152
- return out[0] || null;
153
- } catch {
154
- return null;
155
- }
156
- }
157
144
  function shimTargets() {
158
145
  if (process.platform === "win32") {
159
146
  try {
@@ -176,34 +163,13 @@ function writeShimBlock(file, block) {
176
163
  mkdirSync(dirname(file), { recursive: true });
177
164
  writeFileSync(file, content);
178
165
  }
179
- function installClaudeShim(shieldPath) {
180
- const real = resolveRealClaude();
181
- if (!real) {
182
- console.log(" (Claude Code not found on PATH \u2014 skipped auto-shield. Install it, then re-run `login`.)");
183
- return;
184
- }
185
- const node = process.execPath.replace(/\\/g, "/");
186
- const shield = shieldPath.replace(/\\/g, "/");
187
- const win = process.platform === "win32";
188
- const block = win ? `${SHIM_BEGIN}
189
- function claude { & "${node}" "${shield}" -- "${real}" @args }
190
- ${SHIM_END}` : `${SHIM_BEGIN}
191
- claude() { "${node}" "${shield}" -- "${real}" "$@"; }
192
- ${SHIM_END}`;
193
- const targets = shimTargets();
194
- if (targets.length === 0) {
195
- console.log(" (No shell profile found for auto-shield; run `claude` via `solongate shield -- claude` manually.)");
196
- return;
197
- }
198
- for (const file of targets) {
166
+ function removeClaudeShim() {
167
+ for (const file of shimTargets()) {
199
168
  try {
200
- writeShimBlock(file, block);
201
- console.log(` Auto-shield on: \`claude\` now masks secrets (${file})`);
202
- } catch (e) {
203
- console.log(` (Could not enable auto-shield in ${file}: ${e.message})`);
169
+ writeShimBlock(file, null);
170
+ } catch {
204
171
  }
205
172
  }
206
- console.log(" Open a NEW terminal for it to take effect.");
207
173
  }
208
174
  async function runGlobalInstall(opts = {}) {
209
175
  const p = globalPaths();
@@ -231,7 +197,7 @@ async function runGlobalInstall(opts = {}) {
231
197
  writeFileSync(join(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
232
198
  writeFileSync(join(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
233
199
  console.log(` Installed hooks \u2192 ${p.hooksDir}`);
234
- installClaudeShim(join(p.hooksDir, "shield.mjs"));
200
+ removeClaudeShim();
235
201
  writeFileSync(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
236
202
  console.log(` Wrote ${p.configPath}`);
237
203
  let existing = {};
@@ -0,0 +1 @@
1
+ export declare function runLogsServer(): Promise<void>;
@@ -0,0 +1,174 @@
1
+ // src/logs-server.ts
2
+ import { createServer } from "http";
3
+ import { readFileSync, statSync } from "fs";
4
+ import { resolve, join, isAbsolute } from "path";
5
+ import { homedir } from "os";
6
+ import { readdirSync } from "fs";
7
+ var LOG_FILENAME = "solongate-audit.jsonl";
8
+ var DEFAULT_PORT = 8788;
9
+ function allowedOrigins() {
10
+ const base = [
11
+ "https://dashboard.solongate.com",
12
+ "http://localhost:3000",
13
+ "http://localhost:3005",
14
+ "http://127.0.0.1:3000",
15
+ "http://127.0.0.1:3005"
16
+ ];
17
+ const extra = (process.env.SOLONGATE_DASHBOARD_ORIGIN || "").split(",").map((s) => s.trim()).filter(Boolean);
18
+ return /* @__PURE__ */ new Set([...base, ...extra]);
19
+ }
20
+ function resolveLocalLogDir(rawPath) {
21
+ const dir = String(rawPath || "").trim().replace(/[\\/]+$/, "");
22
+ if (!dir) return null;
23
+ if (isAbsolute(dir)) return dir;
24
+ return resolve(homedir(), ".solongate", "local-logs");
25
+ }
26
+ async function findLogDir() {
27
+ const base = resolve(homedir(), ".solongate");
28
+ try {
29
+ const files = readdirSync(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
30
+ for (const f of files) {
31
+ try {
32
+ const c = JSON.parse(readFileSync(join(base, f), "utf-8"));
33
+ const p = c?.security?.localLogs?.path;
34
+ if (typeof p === "string" && p.trim()) return { dir: resolveLocalLogDir(p), configured: p };
35
+ } catch {
36
+ }
37
+ }
38
+ } catch {
39
+ }
40
+ try {
41
+ const cfgRaw = readFileSync(join(base, "cloud-guard.json"), "utf-8");
42
+ const { apiKey, apiUrl } = JSON.parse(cfgRaw);
43
+ if (apiKey) {
44
+ const url = `${apiUrl || "https://api.solongate.com"}/api/v1/policies/active`;
45
+ const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
46
+ if (res.ok) {
47
+ const body = await res.json();
48
+ const p = body?.security?.localLogs?.path;
49
+ if (typeof p === "string" && p.trim()) return { dir: resolveLocalLogDir(p), configured: p };
50
+ }
51
+ }
52
+ } catch {
53
+ }
54
+ return { dir: null, configured: null };
55
+ }
56
+ function setCors(req, res) {
57
+ const origin = req.headers.origin;
58
+ if (origin && allowedOrigins().has(origin)) {
59
+ res.setHeader("Access-Control-Allow-Origin", origin);
60
+ res.setHeader("Vary", "Origin");
61
+ }
62
+ if (req.headers["access-control-request-private-network"] === "true") {
63
+ res.setHeader("Access-Control-Allow-Private-Network", "true");
64
+ }
65
+ res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
66
+ res.setHeader("Access-Control-Allow-Headers", "If-Modified-Since, Content-Type");
67
+ }
68
+ function fileInfo(dir) {
69
+ if (!dir) return { file: null, exists: false, size: 0, mtimeMs: 0 };
70
+ const file = join(dir, LOG_FILENAME);
71
+ try {
72
+ const st = statSync(file);
73
+ return { file, exists: true, size: st.size, mtimeMs: st.mtimeMs };
74
+ } catch {
75
+ return { file, exists: false, size: 0, mtimeMs: 0 };
76
+ }
77
+ }
78
+ async function runLogsServer() {
79
+ const argv = process.argv.slice(3);
80
+ const portArg = argv[argv.indexOf("--port") + 1];
81
+ const port = Number(process.env.SOLONGATE_LOGS_PORT || (argv.includes("--port") ? portArg : "") || DEFAULT_PORT) || DEFAULT_PORT;
82
+ const server = createServer(async (req, res) => {
83
+ setCors(req, res);
84
+ if (req.method === "OPTIONS") {
85
+ res.writeHead(204);
86
+ res.end();
87
+ return;
88
+ }
89
+ if (req.method !== "GET") {
90
+ res.writeHead(405);
91
+ res.end();
92
+ return;
93
+ }
94
+ const url = (req.url || "/").split("?")[0];
95
+ const { dir, configured } = await findLogDir();
96
+ if (url === "/health") {
97
+ const info = fileInfo(dir);
98
+ res.writeHead(200, { "Content-Type": "application/json" });
99
+ res.end(JSON.stringify({
100
+ ok: true,
101
+ agent: "solongate-logs-server",
102
+ configuredPath: configured,
103
+ resolvedDir: dir,
104
+ file: info.file,
105
+ exists: info.exists,
106
+ size: info.size,
107
+ mtime: info.mtimeMs ? new Date(info.mtimeMs).toISOString() : null
108
+ }));
109
+ return;
110
+ }
111
+ if (url === "/local-logs") {
112
+ const info = fileInfo(dir);
113
+ if (!dir) {
114
+ res.writeHead(200, { "Content-Type": "text/plain", "X-Solongate-Configured": "0" });
115
+ res.end("");
116
+ return;
117
+ }
118
+ if (!info.exists) {
119
+ res.writeHead(200, { "Content-Type": "text/plain", "X-Solongate-Exists": "0" });
120
+ res.end("");
121
+ return;
122
+ }
123
+ const lastMod = new Date(info.mtimeMs).toUTCString();
124
+ const since = req.headers["if-modified-since"];
125
+ if (since && new Date(since).getTime() >= Math.floor(info.mtimeMs / 1e3) * 1e3) {
126
+ res.writeHead(304, { "Last-Modified": lastMod });
127
+ res.end();
128
+ return;
129
+ }
130
+ try {
131
+ const text = readFileSync(info.file, "utf-8");
132
+ res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", "Last-Modified": lastMod, "X-Solongate-Exists": "1" });
133
+ res.end(text);
134
+ } catch {
135
+ res.writeHead(500);
136
+ res.end("read error");
137
+ }
138
+ return;
139
+ }
140
+ res.writeHead(404);
141
+ res.end("not found");
142
+ });
143
+ server.listen(port, "127.0.0.1", async () => {
144
+ const { dir, configured } = await findLogDir();
145
+ process.stdout.write(`[SolonGate] Local logs agent listening on http://127.0.0.1:${port}
146
+ `);
147
+ if (configured) {
148
+ process.stdout.write(`[SolonGate] Configured path: ${configured}
149
+ `);
150
+ if (dir && dir !== configured.trim().replace(/[\\/]+$/, "")) {
151
+ process.stdout.write(`[SolonGate] That path isn't absolute on this machine \u2014 reading from: ${dir}
152
+ `);
153
+ }
154
+ } else {
155
+ process.stdout.write(`[SolonGate] Local log storage not configured yet (set it in dashboard Settings).
156
+ `);
157
+ }
158
+ process.stdout.write(`[SolonGate] Keep this running; the dashboard reads your logs live from here. Ctrl+C to stop.
159
+ `);
160
+ });
161
+ server.on("error", (err) => {
162
+ if (err.code === "EADDRINUSE") {
163
+ process.stderr.write(`[SolonGate] Port ${port} is already in use. Pass --port <n> or set SOLONGATE_LOGS_PORT.
164
+ `);
165
+ } else {
166
+ process.stderr.write(`[SolonGate] Local logs agent error: ${err.message}
167
+ `);
168
+ }
169
+ process.exit(1);
170
+ });
171
+ }
172
+ export {
173
+ runLogsServer
174
+ };
package/dist/pull-push.js CHANGED
File without changes
package/dist/shield.js CHANGED
@@ -17,14 +17,12 @@ var DLP_PATTERNS = [
17
17
  { name: "GitHub fine-grained PAT", re: /github_pat_[A-Za-z0-9_]{20,}/g },
18
18
  { name: "GitLab token", re: /glpat-[A-Za-z0-9_-]{20,}/g },
19
19
  { name: "Slack token", re: /xox[baprs]-[A-Za-z0-9-]{10,}/g },
20
- { name: "Google API key", re: /AIza[0-9A-Za-z_-]{35}/g },
21
20
  { name: "Stripe key", re: /[sr]k_(live|test)_[A-Za-z0-9]{20,}/g },
22
21
  { name: "SendGrid key", re: /SG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}/g },
23
22
  { name: "Twilio key", re: /SK[0-9a-fA-F]{32}/g },
24
23
  { name: "npm token", re: /npm_[A-Za-z0-9]{36}/g },
25
24
  { name: "JWT", re: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g },
26
- { name: "Bearer token", re: /bearer\s+[A-Za-z0-9._-]{20,}/gi },
27
- { name: "secret assignment", re: /(api[_-]?key|secret|token|password|passwd|access[_-]?key)["']?\s*[:=]\s*["']?[A-Za-z0-9/+_.-]{12,}/gi }
25
+ { name: "Bearer token", re: /bearer\s+[A-Za-z0-9._-]{20,}/gi }
28
26
  ];
29
27
  function findCacheFile() {
30
28
  const dir = resolve(homedir(), ".solongate");
@@ -55,11 +53,104 @@ function loadCfg() {
55
53
  if (f && existsSync(f)) {
56
54
  const c = JSON.parse(readFileSync(f, "utf-8"));
57
55
  const d = c?.security?.dlpRedact;
58
- if (d && Array.isArray(d.patterns)) return { patterns: d.patterns, custom: Array.isArray(d.custom) ? d.custom : [] };
56
+ const g = c?.security?.ghost;
57
+ const ghost = g && Array.isArray(g.patterns) ? g.patterns : [];
58
+ if (d && Array.isArray(d.patterns)) return { patterns: d.patterns, custom: Array.isArray(d.custom) ? d.custom : [], ghost };
59
+ return { patterns: DLP_PATTERNS.map((p) => p.name), custom: [], ghost };
59
60
  }
60
61
  } catch {
61
62
  }
62
- return { patterns: DLP_PATTERNS.map((p) => p.name), custom: [] };
63
+ return { patterns: DLP_PATTERNS.map((p) => p.name), custom: [], ghost: [] };
64
+ }
65
+ function ghostGlobToRegExp(glob) {
66
+ let re = "";
67
+ for (let i = 0; i < glob.length; i++) {
68
+ const c = glob[i];
69
+ if (c === "*") {
70
+ if (glob[i + 1] === "*") {
71
+ re += ".*";
72
+ i++;
73
+ } else re += "[^/]*";
74
+ } else if (c === "?") re += "[^/]";
75
+ else if ("\\^$.|+()[]{}".indexOf(c) !== -1) re += "\\" + c;
76
+ else re += c;
77
+ }
78
+ try {
79
+ return new RegExp("^" + re + "$");
80
+ } catch {
81
+ return null;
82
+ }
83
+ }
84
+ function ghostMatch(targetPath, patterns) {
85
+ if (!targetPath || !Array.isArray(patterns) || patterns.length === 0) return false;
86
+ const norm = String(targetPath).replace(/\\/g, "/").replace(/\/+$/, "");
87
+ if (!norm) return false;
88
+ const segments = norm.split("/").filter(Boolean);
89
+ const base = segments.length ? segments[segments.length - 1] : norm;
90
+ for (let pat of patterns) {
91
+ pat = String(pat || "").trim();
92
+ if (!pat) continue;
93
+ let dirOnly = false;
94
+ if (pat.endsWith("/")) {
95
+ dirOnly = true;
96
+ pat = pat.slice(0, -1);
97
+ }
98
+ if (!pat) continue;
99
+ const hasSlash = pat.indexOf("/") !== -1;
100
+ const hasWild = /[*?]/.test(pat);
101
+ const re = ghostGlobToRegExp(pat);
102
+ if (!re) continue;
103
+ if (dirOnly) {
104
+ if (!hasSlash && !hasWild) {
105
+ if (segments.indexOf(pat) !== -1) return true;
106
+ continue;
107
+ }
108
+ let acc = "";
109
+ for (const s of segments) {
110
+ acc = acc ? acc + "/" + s : s;
111
+ if (re.test(acc) || re.test(s)) return true;
112
+ }
113
+ continue;
114
+ }
115
+ if (!hasSlash) {
116
+ if (re.test(base)) return true;
117
+ if (segments.some((s) => re.test(s))) return true;
118
+ continue;
119
+ }
120
+ if (re.test(norm)) return true;
121
+ }
122
+ return false;
123
+ }
124
+ function ghostCleanToken(tok) {
125
+ let t = String(tok || "").trim();
126
+ t = t.replace(/^[<>|;&(]+/, "").replace(/[);&|]+$/, "");
127
+ t = t.replace(/^['"]+/, "").replace(/['"]+$/, "");
128
+ t = t.replace(/^\d*>>?/, "");
129
+ return t.trim();
130
+ }
131
+ function ghostStripLines(text, pats) {
132
+ if (!Array.isArray(pats) || pats.length === 0) return text;
133
+ const lines = String(text).split("\n");
134
+ const kept = [];
135
+ for (const line of lines) {
136
+ const trimmed = line.trim();
137
+ if (!trimmed) {
138
+ kept.push(line);
139
+ continue;
140
+ }
141
+ if (ghostMatch(trimmed, pats)) continue;
142
+ const toks = trimmed.split(/\s+/);
143
+ const anyHit = toks.some((t) => ghostMatch(ghostCleanToken(t), pats));
144
+ if (!anyHit) {
145
+ kept.push(line);
146
+ continue;
147
+ }
148
+ if (toks.length > 3) continue;
149
+ const remaining = toks.filter((t) => !ghostMatch(ghostCleanToken(t), pats));
150
+ if (remaining.length === 0) continue;
151
+ kept.push(remaining.join(" "));
152
+ }
153
+ return kept.join("\n");
63
154
  }
64
155
  function dlpGlobToRe(glob, flags) {
65
156
  let re = "";
@@ -84,8 +175,16 @@ function redactString(s, cfg) {
84
175
  return out;
85
176
  }
86
177
  function redactDeep(value, cfg) {
87
- if (typeof value === "string") return redactString(value, cfg);
88
- if (Array.isArray(value)) return value.map((v) => redactDeep(v, cfg));
178
+ const ghost = cfg && Array.isArray(cfg.ghost) ? cfg.ghost : null;
179
+ if (typeof value === "string") {
180
+ let out = redactString(value, cfg);
181
+ if (ghost && ghost.length) out = ghostStripLines(out, ghost);
182
+ return out;
183
+ }
184
+ if (Array.isArray(value)) {
185
+ const arr = ghost && ghost.length ? value.filter((v) => !(typeof v === "string" && ghostMatch(v.trim(), ghost))) : value;
186
+ return arr.map((v) => redactDeep(v, cfg));
187
+ }
89
188
  if (value && typeof value === "object") {
90
189
  const out = {};
91
190
  for (const [k, v] of Object.entries(value)) out[k] = redactDeep(v, cfg);
@@ -1 +1 @@
1
- {"tool":"Bash","ts":1782827530142}
1
+ {"tool":"Bash","ts":1783610904106}
@@ -0,0 +1 @@
1
+ {"ms":2391,"ts":1783610920233,"tool":"Bash","session":"fc94b672-fd99-4d52-9105-c77c7906d9f0"}
@@ -1 +1 @@
1
- 1782827550450
1
+ 1783610904106