bazis 0.117.1 → 0.118.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.
@@ -232,7 +232,7 @@ export class CodexAppServerClient implements CodexClient {
232
232
  if (method === "account/updated") this.modelCache = undefined;
233
233
  }, () => { this.clearLogin(); this.modelCache = undefined; });
234
234
  const initialized = await client.request("initialize", {
235
- clientInfo: { name: "bazis", title: "Bazis", version: "0.117.1" }, capabilities: { experimentalApi: true },
235
+ clientInfo: { name: "bazis", title: "Bazis", version: "0.118.0" }, capabilities: { experimentalApi: true },
236
236
  }, 15_000, signal);
237
237
  if (resolve(text(initialized.codexHome, 4096)) !== this.paths.home) throw new CodexError("PROTOCOL_ERROR");
238
238
  client.notify("initialized");
@@ -1,3 +1,5 @@
1
+ import { rememberSecretValue } from "../../../library/redaction";
2
+
1
3
  const REDACTED = "***";
2
4
 
3
5
  /**
@@ -9,6 +11,8 @@ export class Secret {
9
11
 
10
12
  public constructor(value: string) {
11
13
  this.#value = value;
14
+ // Logs and errors drop the value even where it was pasted as plain text.
15
+ rememberSecretValue(value);
12
16
  }
13
17
 
14
18
  public reveal(): string {
@@ -607,7 +607,7 @@ function row(v: unknown): OwnedStoreRegistryRowV1 {
607
607
  bad();
608
608
  return Object.freeze(out as OwnedStoreRegistryRowV1);
609
609
  }
610
- export function validateOwnedStoreRegistryV1(rows: unknown, defs: readonly OrmOwnedStoreDefinitionV1[]): readonly OwnedStoreRegistryRowV1[] {
610
+ export function validateOwnedStoreRegistryV1(rows: unknown, _defs: readonly OrmOwnedStoreDefinitionV1[]): readonly OwnedStoreRegistryRowV1[] {
611
611
  const out = dense(rows, row, 4096);
612
612
  const keys = new Set<string>();
613
613
  for (const x of out) {
@@ -619,9 +619,9 @@ export function validateOwnedStoreRegistryV1(rows: unknown, defs: readonly OrmOw
619
619
  if (keys.has(x.storeKey))
620
620
  throw failure("ORM_OWNED_STORE_OWNERSHIP_CONFLICT");
621
621
  keys.add(x.storeKey);
622
- const d = defs.find(y => y.storeKey === x.storeKey);
623
- if (d && (x.formatVersion !== String(d.formatVersion) || x.ownedSchema !== d.ownedScope.schema || x.tablePrefix !== d.ownedScope.tablePrefix))
624
- throw failure("ORM_OWNED_STORE_IDENTITY_MISMATCH");
622
+ // A row that differs from the code (format version, scope) is refused by
623
+ // admission's assertCurrent, after the row is recorded for the explainer,
624
+ // so the message can name what changed.
625
625
  }
626
626
  for (let i = 0; i < out.length; i++)
627
627
  for (let j = i + 1; j < out.length; j++) {
@@ -47,6 +47,42 @@ export function redactTraceValues<T>(values: readonly T[], options: SensitiveRed
47
47
  return values.map(() => replacement);
48
48
  }
49
49
 
50
+ /** Values shorter than this are not remembered: masking them would hide ordinary words ("postgres"). */
51
+ export const MIN_REMEMBERED_SECRET_LENGTH = 10;
52
+ const rememberedSecrets = new Set<string>();
53
+ let longestFirst: readonly string[] = [];
54
+
55
+ /**
56
+ * Remembers a secret value: from now on every redaction removes it wherever it
57
+ * appears, also in plain text without a marker such as `password=`. `Secret`
58
+ * calls it for every configuration secret. Process-wide; values shorter than
59
+ * {@link MIN_REMEMBERED_SECRET_LENGTH} are recognised only by their shape.
60
+ */
61
+ export function rememberSecretValue(value: string): void {
62
+ // A connection URL keeps naming its server: only its password is the secret,
63
+ // and the userinfo pattern already hides user:password@ in the URL itself.
64
+ const password = urlPassword(value);
65
+ if (password !== undefined) {
66
+ if (password !== "") rememberSecretValue(password);
67
+ return;
68
+ }
69
+ if (value.length < MIN_REMEMBERED_SECRET_LENGTH || rememberedSecrets.has(value)) return;
70
+ rememberedSecrets.add(value);
71
+ // A longer value first, so a secret that contains another one goes whole.
72
+ longestFirst = [...rememberedSecrets].sort((left, right) => right.length - left.length);
73
+ }
74
+
75
+ /** The decoded password of a URL with userinfo (`""` for user only), or undefined for other values. */
76
+ function urlPassword(value: string): string | undefined {
77
+ if (!/^[a-z][a-z0-9+.-]*:\/\/[^\s/?#@]+@/i.test(value)) return undefined;
78
+ try {
79
+ const url = new URL(value);
80
+ return decodeURIComponent(url.password);
81
+ } catch {
82
+ return undefined;
83
+ }
84
+ }
85
+
50
86
  export function isSensitiveKey(key: string, options: SensitiveRedactionOptions = {}): boolean {
51
87
  return keyIsSensitive(key, normalizeOptions(options));
52
88
  }
@@ -177,7 +213,9 @@ function normalizeKey(key: string): string {
177
213
  }
178
214
 
179
215
  function redactText(value: string, replacement: string): string {
180
- return value
216
+ let text = value;
217
+ for (const secret of longestFirst) if (text.includes(secret)) text = text.replaceAll(secret, replacement);
218
+ return text
181
219
  .replace(SECRET_TEXT_PATTERN, (_match, name: string, separator: string) => `${name}${separator}${replacement}`)
182
220
  .replace(BEARER_TEXT_PATTERN, (_match, scheme: string) => `${scheme} ${replacement}`)
183
221
  .replace(URL_USERINFO_PATTERN, (_match, scheme: string, userinfo: string) =>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.117.1",
3
+ "version": "0.118.0",
4
4
  "description": "Modular backend framework for Bun in TypeScript: DI wired by codegen, HTTP, a PostgreSQL ORM, JWT, WebSocket, agents and a CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Alexander Alenin",