@i4ctime/q-ring 0.13.1 → 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/index.js CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  checkSSRF,
7
7
  clearMemory,
8
8
  collapseEnvironment,
9
+ countLegacyApprovals,
9
10
  deleteSecret,
10
11
  detectAnomalies,
11
12
  disableHook,
@@ -36,13 +37,14 @@ import {
36
37
  remember,
37
38
  removeHook,
38
39
  revokeApproval,
40
+ serviceForScope,
39
41
  setSecret,
40
42
  tunnelCreate,
41
43
  tunnelDestroy,
42
44
  tunnelList,
43
45
  tunnelRead,
44
46
  verifyAuditChain
45
- } from "./chunk-Z7UJFHVE.js";
47
+ } from "./chunk-C2TFJ2EH.js";
46
48
 
47
49
  // src/cli/commands.ts
48
50
  import { Command, Help } from "commander";
@@ -1881,12 +1883,39 @@ function registerValidationCommands(program2) {
1881
1883
 
1882
1884
  // src/core/exec.ts
1883
1885
  import { spawn } from "child_process";
1886
+ import { StringDecoder } from "string_decoder";
1884
1887
  import { Transform } from "stream";
1885
1888
  var BUILTIN_PROFILES = {
1886
1889
  unrestricted: { name: "unrestricted" },
1887
1890
  restricted: {
1888
1891
  name: "restricted",
1889
- denyCommands: ["curl", "wget", "ssh", "scp", "nc", "netcat", "ncat"],
1892
+ denyCommands: [
1893
+ // Network tools.
1894
+ "curl",
1895
+ "wget",
1896
+ "ssh",
1897
+ "scp",
1898
+ "nc",
1899
+ "netcat",
1900
+ "ncat",
1901
+ // Interpreters and shells: given the secret env vars, `python -c`,
1902
+ // `node -e`, `bash -c`, etc. can perform arbitrary network I/O and
1903
+ // exfiltrate them, defeating allowNetwork. Denied by default in
1904
+ // `restricted`; to run them with secrets use a custom profile in
1905
+ // .q-ring.json, or the `ci` / `unrestricted` profile.
1906
+ "python",
1907
+ "python2",
1908
+ "python3",
1909
+ "node",
1910
+ "deno",
1911
+ "bun",
1912
+ "perl",
1913
+ "ruby",
1914
+ "php",
1915
+ "sh",
1916
+ "bash",
1917
+ "zsh"
1918
+ ],
1890
1919
  maxRuntimeSeconds: 30,
1891
1920
  allowNetwork: false,
1892
1921
  stripEnvVars: ["HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY"]
@@ -1906,6 +1935,11 @@ var RedactionTransform = class extends Transform {
1906
1935
  patterns = [];
1907
1936
  tail = "";
1908
1937
  maxLen = 0;
1938
+ // Decodes UTF-8 across chunk boundaries: a multi-byte character split between
1939
+ // two Buffer chunks would otherwise be corrupted to U+FFFD by chunk.toString()
1940
+ // BEFORE matching runs, letting a multi-byte secret slip through unredacted.
1941
+ // StringDecoder buffers the incomplete trailing bytes until the rest arrives.
1942
+ decoder = new StringDecoder("utf8");
1909
1943
  constructor(secretsToRedact) {
1910
1944
  super();
1911
1945
  const validSecrets = secretsToRedact.filter((s) => s.length > 5);
@@ -1923,7 +1957,8 @@ var RedactionTransform = class extends Transform {
1923
1957
  this.push(chunk);
1924
1958
  return callback();
1925
1959
  }
1926
- const text = this.tail + chunk.toString();
1960
+ const decoded = typeof chunk === "string" ? chunk : this.decoder.write(chunk);
1961
+ const text = this.tail + decoded;
1927
1962
  let redacted = text;
1928
1963
  for (const { value, replacement } of this.patterns) {
1929
1964
  redacted = redacted.split(value).join(replacement);
@@ -1939,8 +1974,8 @@ var RedactionTransform = class extends Transform {
1939
1974
  callback();
1940
1975
  }
1941
1976
  _flush(callback) {
1942
- if (this.tail) {
1943
- let final = this.tail;
1977
+ let final = this.tail + this.decoder.end();
1978
+ if (final) {
1944
1979
  for (const { value, replacement } of this.patterns) {
1945
1980
  final = final.split(value).join(replacement);
1946
1981
  }
@@ -2457,7 +2492,7 @@ function registerToolingCommands(program2) {
2457
2492
  }
2458
2493
  });
2459
2494
  program2.command("status").description("Launch the quantum status dashboard in your browser").option("--port <port>", "Port to serve on", "9876").option("--no-open", "Don't auto-open the browser").action(async (cmd) => {
2460
- const { startDashboardServer } = await import("./dashboard-2IGUR7PA.js");
2495
+ const { startDashboardServer } = await import("./dashboard-PYYAED45.js");
2461
2496
  const { exec } = await import("child_process");
2462
2497
  const { platform } = await import("os");
2463
2498
  const port = Number(cmd.port);
@@ -3245,6 +3280,7 @@ function registerSecurityCommands(program2) {
3245
3280
  ).option("-g, --global", "Global scope").option("-p, --project", "Project scope").option("--project-path <path>", "Explicit project path").option("--for <seconds>", "Duration of approval in seconds", parseInt, 3600).option("--reason <text>", "Reason for granting approval").option("--revoke", "Revoke an existing approval").option("--list", "List all approvals").action((key, cmd) => {
3246
3281
  const opts = buildOpts(cmd);
3247
3282
  const scope = opts.scope ?? "global";
3283
+ const service = serviceForScope(scope, opts);
3248
3284
  if (cmd.list) {
3249
3285
  const approvals = listApprovals();
3250
3286
  if (approvals.length === 0) {
@@ -3266,7 +3302,7 @@ function registerSecurityCommands(program2) {
3266
3302
  return;
3267
3303
  }
3268
3304
  if (cmd.revoke) {
3269
- const revoked = revokeApproval(key, scope);
3305
+ const revoked = revokeApproval(key, scope, service);
3270
3306
  if (revoked) {
3271
3307
  console.log(
3272
3308
  `${SYMBOLS.check} ${c.yellow("revoked")} approval for ${c.bold(key)}`
@@ -3276,7 +3312,7 @@ function registerSecurityCommands(program2) {
3276
3312
  }
3277
3313
  return;
3278
3314
  }
3279
- const entry = grantApproval(key, scope, cmd.for, {
3315
+ const entry = grantApproval(key, scope, service, cmd.for, {
3280
3316
  reason: cmd.reason ?? "manual approval"
3281
3317
  });
3282
3318
  console.log(
@@ -3482,6 +3518,21 @@ function checkPolicy() {
3482
3518
  detail: active.length ? `active: ${active.join(", ")}` : 'none configured (add a "policy" section to .q-ring.json to enable)'
3483
3519
  };
3484
3520
  }
3521
+ function checkLegacyApprovals() {
3522
+ const n = countLegacyApprovals();
3523
+ if (n === 0) {
3524
+ return {
3525
+ name: "approval bindings",
3526
+ status: "ok",
3527
+ detail: "no legacy approvals; all bound to a project/team/org identity"
3528
+ };
3529
+ }
3530
+ return {
3531
+ name: "approval bindings",
3532
+ status: "warn",
3533
+ detail: `${n} pre-v0.14 approval(s) have no project binding and will NOT grant access \u2014 re-run \`qring approve\` for each`
3534
+ };
3535
+ }
3485
3536
  function checkMcpBinary() {
3486
3537
  const exts = process.platform === "win32" ? [".cmd", ".exe", ".bat", ""] : [""];
3487
3538
  for (const dir of (process.env.PATH ?? "").split(delimiter)) {
@@ -3511,6 +3562,7 @@ function registerDoctorCommand(program2) {
3511
3562
  checkAuditLog(),
3512
3563
  checkManifest(projectPath),
3513
3564
  checkPolicy(),
3565
+ checkLegacyApprovals(),
3514
3566
  checkMcpBinary()
3515
3567
  ];
3516
3568
  const failed = checks.filter((r) => r.status === "fail").length;