vault-cortex 0.11.0 → 0.12.0-beta.60

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/env.js CHANGED
@@ -94,17 +94,28 @@ MEMORY_DIR=About Me
94
94
  # Host port to expose (default: 8000).
95
95
  PORT=8000
96
96
 
97
- # Client-IP trust for OAuth rate limiting and logs (defaults shown).
98
- # With nothing in front of the server, leave both untouched — each visitor
99
- # is identified by their own connection, and faked Forwarded/X-Forwarded-For
100
- # headers are ignored.
101
- # With a proxy or tunnel you control in front (Caddy, nginx, Cloudflare
102
- # Tunnel), set TRUST_PROXY_HOPS=1 so visitors are told apart by their real
103
- # IP — otherwise they all share one rate-limit budget.
104
- # Set TRUST_FORWARDED_HEADER=true only if the proxy reports each visitor's
105
- # IP in the RFC 7239 Forwarded header (e.g. AWS API Gateway).
97
+ # These two settings choose the client IP used for OAuth rate limiting and
98
+ # logs (defaults shown below). Without the right values, every visitor
99
+ # behind a proxy shares one rate-limit budget. TRUST_PROXY_HOPS counts the
100
+ # proxies you control that write X-Forwarded-For; TRUST_FORWARDED_HOPS
101
+ # counts the ones that write the RFC 7239 Forwarded header
102
+ # (https://www.rfc-editor.org/rfc/rfc7239), and 0 ignores that header.
103
+ #
104
+ # What sits in front of the server TRUST_PROXY_HOPS TRUST_FORWARDED_HOPS Client IP is
105
+ # Nothing (default) 0 0 the visitor (socket peer)
106
+ # A proxy or tunnel you control 1 0 the visitor (X-Forwarded-For)
107
+ # (Caddy, nginx, Cloudflare Tunnel)
108
+ # A proxy that reports visitors in 1 1 the visitor (Forwarded)
109
+ # Forwarded (e.g. AWS API Gateway)
110
+ # A CDN in front of that proxy, and 1 2 the visitor (Forwarded)
111
+ # the CDN is the only way to reach it
112
+ #
113
+ # When the Forwarded header is read (TRUST_FORWARDED_HOPS above 0), it wins
114
+ # and TRUST_PROXY_HOPS is not consulted; TRUST_PROXY_HOPS is the fallback
115
+ # when that header is ignored or absent. Faked headers from visitors are
116
+ # ignored in every row — only the counted proxies are trusted.
106
117
  # TRUST_PROXY_HOPS=0
107
- # TRUST_FORWARDED_HEADER=false
118
+ # TRUST_FORWARDED_HOPS=0
108
119
 
109
120
  # Log verbosity: debug | info | warn | error (default: info).
110
121
  LOG_LEVEL=info
@@ -221,17 +232,28 @@ MEMORY_DIR=About Me
221
232
  # Host port to expose (default: 8000).
222
233
  PORT=8000
223
234
 
224
- # Client-IP trust for OAuth rate limiting and logs (defaults shown).
225
- # With nothing in front of the server, leave both untouched — each visitor
226
- # is identified by their own connection, and faked Forwarded/X-Forwarded-For
227
- # headers are ignored.
228
- # With a proxy or tunnel you control in front (Caddy, nginx, Cloudflare
229
- # Tunnel), set TRUST_PROXY_HOPS=1 so visitors are told apart by their real
230
- # IP — otherwise they all share one rate-limit budget.
231
- # Set TRUST_FORWARDED_HEADER=true only if the proxy reports each visitor's
232
- # IP in the RFC 7239 Forwarded header (e.g. AWS API Gateway).
235
+ # These two settings choose the client IP used for OAuth rate limiting and
236
+ # logs (defaults shown below). Without the right values, every visitor
237
+ # behind a proxy shares one rate-limit budget. TRUST_PROXY_HOPS counts the
238
+ # proxies you control that write X-Forwarded-For; TRUST_FORWARDED_HOPS
239
+ # counts the ones that write the RFC 7239 Forwarded header
240
+ # (https://www.rfc-editor.org/rfc/rfc7239), and 0 ignores that header.
241
+ #
242
+ # What sits in front of the server TRUST_PROXY_HOPS TRUST_FORWARDED_HOPS Client IP is
243
+ # Nothing (default) 0 0 the visitor (socket peer)
244
+ # A proxy or tunnel you control 1 0 the visitor (X-Forwarded-For)
245
+ # (Caddy, nginx, Cloudflare Tunnel)
246
+ # A proxy that reports visitors in 1 1 the visitor (Forwarded)
247
+ # Forwarded (e.g. AWS API Gateway)
248
+ # A CDN in front of that proxy, and 1 2 the visitor (Forwarded)
249
+ # the CDN is the only way to reach it
250
+ #
251
+ # When the Forwarded header is read (TRUST_FORWARDED_HOPS above 0), it wins
252
+ # and TRUST_PROXY_HOPS is not consulted; TRUST_PROXY_HOPS is the fallback
253
+ # when that header is ignored or absent. Faked headers from visitors are
254
+ # ignored in every row — only the counted proxies are trusted.
233
255
  # TRUST_PROXY_HOPS=0
234
- # TRUST_FORWARDED_HEADER=false
256
+ # TRUST_FORWARDED_HOPS=0
235
257
 
236
258
  # Log verbosity: debug | info | warn | error (default: info).
237
259
  LOG_LEVEL=info
package/dist/init.js CHANGED
@@ -6,7 +6,7 @@ import { buildDaemonNotRunningMessage, buildDockerNotInstalledMessage, buildLoca
6
6
  import { healthPollTimeoutMs, healthTimeoutMessage, pollHealth, } from "./docker.js";
7
7
  import { reportPublicUrlProbe } from "./lifecycle.js";
8
8
  import { applyOptionalSettings, askOptionalSettings, derivePublicUrlOverride, } from "./optional-settings.js";
9
- import { buildFilesToWrite, readEnvPort, readEnvPublicUrl, writeFiles, } from "./scaffold.js";
9
+ import { buildFilesToWrite, readEnvPort, readEnvPublicUrl, stripEnvQuotedValues, writeFiles, } from "./scaffold.js";
10
10
  import { generateToken } from "./token.js";
11
11
  import { expandTilde, validateVaultPath } from "./vault.js";
12
12
  const DEFAULT_TARGET_DIR = "./vault-cortex";
@@ -174,9 +174,11 @@ const offerDockerRun = async (params, deps) => {
174
174
  const startNow = await prompts.confirm("Start the server now?", true);
175
175
  if (!startNow)
176
176
  return "not-started";
177
+ const envFilePath = join(targetDir, ".env");
178
+ stripEnvQuotedValues(envFilePath);
177
179
  const containerStarted = docker.dockerRun({
178
180
  mode,
179
- envFilePath: join(targetDir, ".env"),
181
+ envFilePath,
180
182
  port,
181
183
  vaultPath,
182
184
  });
package/dist/lifecycle.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { join, resolve } from "node:path";
2
2
  import { CONTAINER_NAME, healthPollTimeoutMs, healthTimeoutMessage, pollHealth, probeHealth, } from "./docker.js";
3
3
  import { buildDaemonNotRunningMessage, buildDockerNotInstalledMessage, } from "./messages.js";
4
- import { detectMode, hasEnvPublicUrl, readEnvPort, readEnvPublicUrl, readEnvVaultPath, } from "./scaffold.js";
4
+ import { detectMode, hasEnvPublicUrl, readEnvPort, readEnvPublicUrl, readEnvVaultPath, stripEnvQuotedValues, } from "./scaffold.js";
5
5
  import { expandTilde } from "./vault.js";
6
6
  const DEFAULT_TARGET_DIR = "./vault-cortex";
7
7
  /**
@@ -110,6 +110,7 @@ export const recreateContainer = async (params, deps) => {
110
110
  prompts.error(`Could not remove the existing container — check: docker rm -f ${CONTAINER_NAME}`);
111
111
  return 1;
112
112
  }
113
+ stripEnvQuotedValues(deployment.envFilePath);
113
114
  prompts.log("Starting container...");
114
115
  const containerStarted = docker.dockerRun({
115
116
  mode: deployment.mode,
package/dist/scaffold.js CHANGED
@@ -2,8 +2,8 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "
2
2
  import { join } from "node:path";
3
3
  /** Default host port — matches the container's internal port. */
4
4
  export const DEFAULT_PORT = 8000;
5
- /** Matches an active (uncommented) PORT line in a .env file. */
6
- const ENV_PORT_LINE = /^PORT=(\d+)\s*$/m;
5
+ /** Matches an active (uncommented) PORT line, with optional surrounding quotes. */
6
+ const ENV_PORT_LINE = /^PORT=["']?(\d+)["']?\s*$/m;
7
7
  /** Matches an active (uncommented) VAULT_PATH line in a .env file. */
8
8
  const ENV_VAULT_PATH_LINE = /^VAULT_PATH=(.+)\s*$/m;
9
9
  /** Matches an active (uncommented) PUBLIC_URL line. */
@@ -12,6 +12,17 @@ const ENV_PUBLIC_URL_LINE = /^PUBLIC_URL=/m;
12
12
  const ENV_PUBLIC_URL_VALUE_LINE = /^PUBLIC_URL=(.+)\s*$/m;
13
13
  /** Matches an active (uncommented) OBSIDIAN_AUTH_TOKEN line. */
14
14
  const OBSIDIAN_AUTH_TOKEN_LINE = /^OBSIDIAN_AUTH_TOKEN=/m;
15
+ /** Matches an env line whose value is wrapped in matching quotes. */
16
+ const QUOTED_ENV_VALUE = /^([A-Za-z_][A-Za-z0-9_]*=)(["'])(.*)\2(\s*)$/gm;
17
+ /**
18
+ * Strips matching surrounding quotes from a value — `"foo"` → `foo`,
19
+ * `'bar'` → `bar`, `unquoted` → `unquoted`. Only strips when the
20
+ * opening and closing quote characters match.
21
+ */
22
+ const stripSurroundingQuotes = (value) => {
23
+ const quoteMatch = /^(["'])(.*)\1$/.exec(value);
24
+ return quoteMatch ? quoteMatch[2] : value;
25
+ };
15
26
  export const buildFilesToWrite = (envContent) => [
16
27
  // .env holds the bearer token (and possibly a vault password) — owner-only.
17
28
  { name: ".env", content: envContent, mode: 0o600 },
@@ -36,7 +47,8 @@ export const readEnvVaultPath = (envFilePath) => {
36
47
  if (!existsSync(envFilePath))
37
48
  return undefined;
38
49
  const match = ENV_VAULT_PATH_LINE.exec(readFileSync(envFilePath, "utf8"));
39
- return match?.[1].trim();
50
+ const rawValue = match?.[1].trim();
51
+ return rawValue ? stripSurroundingQuotes(rawValue) : undefined;
40
52
  };
41
53
  /**
42
54
  * Returns true when the .env file has an active (uncommented) PUBLIC_URL line.
@@ -63,11 +75,12 @@ export const readEnvPublicUrl = (envFilePath) => {
63
75
  const match = ENV_PUBLIC_URL_VALUE_LINE.exec(readFileSync(envFilePath, "utf8"));
64
76
  // A whitespace-only line matches the regex and trims to "" — normalize to
65
77
  // undefined so the non-empty contract holds ("" is never a legitimate URL).
66
- const publicUrlValue = match?.[1].trim();
78
+ const rawValue = match?.[1].trim();
79
+ const unquotedValue = rawValue ? stripSurroundingQuotes(rawValue) : undefined;
67
80
  // Strip trailing slashes (mirroring askPublicUrl's prompt-side
68
81
  // normalization): consumers append paths to this base, and a hand-edited
69
82
  // `https://host/` would otherwise print broken `//mcp` connect URLs.
70
- const normalizedPublicUrl = publicUrlValue?.replace(/\/+$/, "");
83
+ const normalizedPublicUrl = unquotedValue?.replace(/\/+$/, "");
71
84
  return normalizedPublicUrl || undefined;
72
85
  };
73
86
  /**
@@ -100,6 +113,25 @@ export const patchEnvObsidianToken = (envFilePath, token) => {
100
113
  writeFileSync(envFilePath, patched);
101
114
  return true;
102
115
  };
116
+ /**
117
+ * Strips surrounding quotes from env values in the file. `docker run
118
+ * --env-file` passes quotes literally (`VAULT_NAME="My Vault"` becomes
119
+ * the value `"My Vault"` with embedded quotes), while Compose strips
120
+ * them. Removing quotes makes the file work correctly for both paths.
121
+ * Returns true when the file was modified.
122
+ */
123
+ export const stripEnvQuotedValues = (envFilePath) => {
124
+ if (!existsSync(envFilePath))
125
+ return false;
126
+ const content = readFileSync(envFilePath, "utf8");
127
+ // Reset lastIndex — the /g flag makes the regex stateful.
128
+ QUOTED_ENV_VALUE.lastIndex = 0;
129
+ const sanitized = content.replace(QUOTED_ENV_VALUE, "$1$3$4");
130
+ if (sanitized === content)
131
+ return false;
132
+ writeFileSync(envFilePath, sanitized);
133
+ return true;
134
+ };
103
135
  /**
104
136
  * Writes the files into targetDir (created if missing). Existing files
105
137
  * are never overwritten silently: identical content is skipped, and differing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vault-cortex",
3
- "version": "0.11.0",
3
+ "version": "0.12.0-beta.60",
4
4
  "description": "Set up a Vault Cortex MCP server for your Obsidian vault in one command: npx vault-cortex@latest init",
5
5
  "license": "MIT",
6
6
  "type": "module",