@i4ctime/q-ring 0.13.0 → 0.14.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/mcp.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  tunnelList,
40
40
  tunnelRead,
41
41
  verifyAuditChain
42
- } from "./chunk-CWV3WTPF.js";
42
+ } from "./chunk-NNIEXAW5.js";
43
43
 
44
44
  // src/mcp.ts
45
45
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -710,6 +710,15 @@ function registerSecretTools(server2) {
710
710
  params.sourceProjectPath
711
711
  );
712
712
  if (toolBlock) return toolBlock;
713
+ for (const key of [params.sourceKey, params.targetKey]) {
714
+ const decision = checkKeyReadPolicy(key, void 0, params.sourceProjectPath);
715
+ if (!decision.allowed) {
716
+ return text(
717
+ `Policy Denied: ${decision.reason} (source: ${decision.policySource})`,
718
+ true
719
+ );
720
+ }
721
+ }
713
722
  entangleSecrets(
714
723
  params.sourceKey,
715
724
  {
@@ -2038,12 +2047,39 @@ function runHealthScan(config = {}) {
2038
2047
 
2039
2048
  // src/core/exec.ts
2040
2049
  import { spawn } from "child_process";
2050
+ import { StringDecoder } from "string_decoder";
2041
2051
  import { Transform } from "stream";
2042
2052
  var BUILTIN_PROFILES = {
2043
2053
  unrestricted: { name: "unrestricted" },
2044
2054
  restricted: {
2045
2055
  name: "restricted",
2046
- denyCommands: ["curl", "wget", "ssh", "scp", "nc", "netcat", "ncat"],
2056
+ denyCommands: [
2057
+ // Network tools.
2058
+ "curl",
2059
+ "wget",
2060
+ "ssh",
2061
+ "scp",
2062
+ "nc",
2063
+ "netcat",
2064
+ "ncat",
2065
+ // Interpreters and shells: given the secret env vars, `python -c`,
2066
+ // `node -e`, `bash -c`, etc. can perform arbitrary network I/O and
2067
+ // exfiltrate them, defeating allowNetwork. Denied by default in
2068
+ // `restricted`; to run them with secrets use a custom profile in
2069
+ // .q-ring.json, or the `ci` / `unrestricted` profile.
2070
+ "python",
2071
+ "python2",
2072
+ "python3",
2073
+ "node",
2074
+ "deno",
2075
+ "bun",
2076
+ "perl",
2077
+ "ruby",
2078
+ "php",
2079
+ "sh",
2080
+ "bash",
2081
+ "zsh"
2082
+ ],
2047
2083
  maxRuntimeSeconds: 30,
2048
2084
  allowNetwork: false,
2049
2085
  stripEnvVars: ["HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY"]
@@ -2063,6 +2099,11 @@ var RedactionTransform = class extends Transform {
2063
2099
  patterns = [];
2064
2100
  tail = "";
2065
2101
  maxLen = 0;
2102
+ // Decodes UTF-8 across chunk boundaries: a multi-byte character split between
2103
+ // two Buffer chunks would otherwise be corrupted to U+FFFD by chunk.toString()
2104
+ // BEFORE matching runs, letting a multi-byte secret slip through unredacted.
2105
+ // StringDecoder buffers the incomplete trailing bytes until the rest arrives.
2106
+ decoder = new StringDecoder("utf8");
2066
2107
  constructor(secretsToRedact) {
2067
2108
  super();
2068
2109
  const validSecrets = secretsToRedact.filter((s) => s.length > 5);
@@ -2080,7 +2121,8 @@ var RedactionTransform = class extends Transform {
2080
2121
  this.push(chunk);
2081
2122
  return callback();
2082
2123
  }
2083
- const text2 = this.tail + chunk.toString();
2124
+ const decoded = typeof chunk === "string" ? chunk : this.decoder.write(chunk);
2125
+ const text2 = this.tail + decoded;
2084
2126
  let redacted = text2;
2085
2127
  for (const { value, replacement } of this.patterns) {
2086
2128
  redacted = redacted.split(value).join(replacement);
@@ -2096,8 +2138,8 @@ var RedactionTransform = class extends Transform {
2096
2138
  callback();
2097
2139
  }
2098
2140
  _flush(callback) {
2099
- if (this.tail) {
2100
- let final = this.tail;
2141
+ let final = this.tail + this.decoder.end();
2142
+ if (final) {
2101
2143
  for (const { value, replacement } of this.patterns) {
2102
2144
  final = final.split(value).join(replacement);
2103
2145
  }
@@ -2491,7 +2533,7 @@ function registerToolingTools(server2) {
2491
2533
  "Inject only secrets carrying at least one of these tags. Combinable with `keys` as an AND filter."
2492
2534
  ),
2493
2535
  profile: z9.enum(["unrestricted", "restricted", "ci"]).optional().default("restricted").describe(
2494
- "Exec sandbox profile. 'restricted' (default) limits PATH and inheritable env vars; 'ci' is restricted plus CI-friendly defaults (no TTY); 'unrestricted' inherits the full server environment \u2014 only pick this when you understand the leak risk."
2536
+ "Exec sandbox profile. 'restricted' (default) denies network-tool binaries (curl, wget, ssh, scp, nc, netcat, ncat) AND common interpreters/shells (python, node, deno, bun, perl, ruby, php, sh, bash, zsh) \u2014 since those could otherwise egress the injected secrets \u2014 strips proxy env vars, and caps runtime at 30s. It still is NOT a real OS sandbox (it does not restrict PATH, and some allowed binary could in principle make network calls); for genuinely untrusted commands use OS-level isolation (containers, network namespaces). 'ci' allows network and interpreters with a 300s cap and blocks a few destructive commands; 'unrestricted' inherits the full server environment. Define a custom profile in .q-ring.json to allow specific interpreters with secrets."
2495
2537
  ),
2496
2538
  scope: scope6,
2497
2539
  projectPath: projectPath6,
@@ -2658,7 +2700,7 @@ ${result.stderr}`);
2658
2700
  `Dashboard already running at ${dashboardInstance.url}`
2659
2701
  );
2660
2702
  }
2661
- const { startDashboardServer } = await import("./dashboard-RLMROWIU.js");
2703
+ const { startDashboardServer } = await import("./dashboard-KAUEPLXO.js");
2662
2704
  dashboardInstance = startDashboardServer({ port: params.port });
2663
2705
  return text(
2664
2706
  `Dashboard started at ${dashboardInstance.url}