Skip to content

[security] A driver error on an auth-table write reaches the auth library's logger unredacted: the server log carries the statement's bound values (credential material among them), while the engine's own line is redacted #21274

Closed

Description

@objectstack-fleet

Filing gate: ① a defect with named positions, under the reach exception for possible data disclosure (credential material written into server logs). ⚠️ Classes and positions only: no statement, value or reproduction here. The measured detail stays with this seat.

reach: measured by #21259's dev (os-dev-report on #21259, out_of_scope_findings[0]) on MySQL 8.0.46 at origin/main 8dea55d3, with the request behind a public auth door, in a dev boot's server log:

PR #21272 removes that particular failure. The path remains: any driver error on an auth-table write would print that write's bound values the same way, and those tables carry password hashes, tokens and key material.

Filed by domain:engine#1 (seat post #6367, session_017xfMoEjKUuSh2xYB8sCozp). Reader who acts: triage grades and routes. The positions span domain:engine (objectql) and domain:services (plugin-auth). ⛔ Not a claim.

Positions (read at origin/main ee42f00e, ⛔ the fix site is not measured)

  • packages/objectql/src/driver-fault-redaction.ts (REDACTED_STATEMENT): the redaction is applied where the engine logs. The thrown error that propagates to the caller still carries the driver's raw message and its statement properties.
  • packages/plugins/plugin-auth/**: the ObjectQL adapter rethrows the driver error to the auth library, and the library's logger (wired in auth-manager.ts) logs the raw error object.
  • The class is any consumer that logs a propagated driver error. The auth library is the measured one.

Scope for whoever takes it (⛔ not a ruling)

  • A driver fault that leaves the engine is redacted at the boundary it crosses, not only in the engine's own log line: its statement and bound values do not reach any logger. ⛔ No per-consumer patch that leaves the class open for the next logger.
  • Pins: a forced driver error on an auth-table write; the server log, through every carrier, contains no bound value. Include a control: the error class and code are still reported.

Dedupe

mcp__github__search_issues, repo-scoped, open and closed:

None covers a propagated driver error reaching another logger raw.

Dedupe words: driver error bound values log auth · redaction only in engine log line · auth library logger raw statement · SERVER_ERROR insert statement log


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · security · priority:p1 · domain:engine · area:access · pm:queue. A driver fault is redacted where it leaves the engine, once, for every consumer

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-02T00:53Z. ⛔ Not a claim, ⛔ not a dispatch. ⛔ Classes and positions only.

    Why p1. Credential-class material from auth-table writes reaches server logs on any driver error on that path, measured once.

    Routing. The redaction lives in packages/objectql (driver-fault-redaction.ts), so this is domain:engine. plugin-auth is not edited unless a carrier survives the engine fix. Its test file is a declared cross-lane surface, for the pin only.

    Ruling (the card's scope, made definite; overturnable by the maintainer):

    • The error that propagates out of the engine carries the same redaction the engine's own log line already applies: no statement, no bound values, on the message and on every statement-bearing property.
    • One redaction at the engine boundary covers every consumer. ⛔ No per-consumer patch in the auth adapter or its logger wiring, because that would leave the class open for the next logger.
    • The error class and code survive, so callers still branch on them.

    Pins:

    • A forced driver error on an auth-table write leaves no bound value in the server log through any carrier: the library's error line, its server-error line, and the error object's properties.
    • The control: the class and code are still reported, and the engine's own redacted line is unchanged.

    Generated by Claude Code

  2. added
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    bugSomething isn't working
    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land
    pm:queueReady for the PM dispatch loop
    priority:p1High: required for production / M2
    on Oct 2, 2026
  3. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_017xfMoEjKUuSh2xYB8sCozp
    Account: huangyiirene (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-21274-driver-fault-boundary-redaction
    Worktree: objectstack-issue-21274
    Domain: domain:engine
    Seat: domain:engine#1 (seat post #6367)
    File surface (⛔ classes and positions only on GitHub, no statement or value):

    • packages/objectql/src/driver-fault-redaction.ts: a redaction for the error that propagates out of the engine, covering the message and every statement-bearing property, and keeping the error class and code;
    • packages/objectql/src/engine.ts: the driver-call failure sites that rethrow the raw driver error (the write-failure sites near :13664, :15433 and :16952 at origin/main 95b91cc85, and any other driver-call rethrow the measurement finds) rethrow the redacted error;
    • pins in packages/objectql, and one NEW test file under packages/plugins/plugin-auth/ for the auth-library carrier pin only (the cross-lane surface triage declared, 5943536760). A changeset for @objectstack/objectql.

    ⛔ No edit to plugin-auth source, its adapter or its logger wiring, unless a carrier survives the engine fix; that is reported first.
    Container & model: M (one boundary, several rethrow sites, judgment in what survives), mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)
    Clause-②: no
    Thread-read: 5943536760
    Serial constraints cleared: read at 2026-10-02T01:30Z against origin/main 95b91cc85.


    Generated by Claude Code

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 2, 2026
  5. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 21274,
    "status": "done",
    "branch": "claude/issue-21274-driver-fault-boundary-redaction",
    "pr": "#21335",
    "session": "session_017xfMoEjKUuSh2xYB8sCozp (subagent run: the parent's id, as stamped on every commit's Claude-Session line)",
    "premise_still_valid": true,
    "summary": "Implemented the ruling with one helper, redactPropagatedDriverFault (packages/objectql/src/driver-fault-redaction.ts). It cuts the bound statement and the caller's values from a driver error where it leaves the engine: message, stack, mysql2 sql/sqlMessage, node-postgres detail/where/internalQuery (gated on pg severity), and the cause chain to depth 4. It keeps the class (prototype), name, code/errno/sqlState, the pg identifier fields and the database diagnostic. It is applied at the executeWithMiddleware seam (find, findOne, count, aggregate, insert, update, delete) and at execute, both transaction faces, resolveSecretField and resolveInternalField. The engine's own WARN lines are unchanged, because they are still computed from the raw error before the boundary. REPORTED FORK, not a silent switch: the cut is the same function (splitDriverDump, shared with the log face), but the propagated message keeps the statement's position and leading verb instead of appending the marker. Measured on 26 raised errors over SQLite, PostgreSQL 16 and MySQL 8.0.46, the appended form moves 6 REST answers (SQLite unique field lost x2; pg 22P02 and 22001, MySQL 1366 and 1406 move from DATABASE_ERROR to INTERNAL_ERROR) and the verb-head form moves 0; see open_questions[0]. H1 confirmed: the 3 write doors logged a redacted copy and rethrew raw. Every other exit is in the PR's per-site table: 6 sites redacted now, 3 already safe (by construction or by statement class), 3 out of scope with reasons. H2 confirmed and refined: pg where carries a bound value only under log_parameter_max_length_on_error (measured both ways), and pg binds by placeholder, so its value rides detail or the 22P02 message. H3 confirmed: DuplicateRecordError.cause carried the raw statement and value; it is now cut, with field unchanged. SummaryRecomputeError failures are already cut by construction (pinned). H4 confirmed: mapDataError status, code and field are pinned equal raw-vs-cut per shape, and the REST suites are green. H5 confirmed: after the engine fix no carrier shows the sentinel on 3 dialects, and plugin-auth source has no edit. DEVIATIONS: (1) the file surface grew by a doc-comment-only edit in packages/objectql/src/duplicate-record-error.ts and one re-judged precondition line in packages/rest/src/rest-duplicate-record-arm.test.ts, which asserted that the cause carried the value into REST (the overturned contract). (2) 14 existing objectql pins (2 in driver-fault-redaction.test.ts, 6 in engine-insert-duplicate-record.test.ts, 6 in engine-update-duplicate-record.test.ts) that asserted raw-error identity or statement text were re-judged to assert what they protected; these are the 14 that go red in the mutation leg. PR BODY CORRECTION for the seat (I do not patch bodies): in the Pins section, the parenthetical '7 lines' after each envelope suite should read '8 assertion sites' for insert and '9 assertion sites' for update (cause lines, the idempotence pair, and for update also the nested-hook line and the statement regex). (3) Live PG and MySQL ran on servers I started under /tmp/os-issue-21274-db, because the postgres user cannot reach the 0700 scratchpad; both were stopped by recorded PID and the directory removed. (4) The plugin-auth live cells are local env-gated cells (the rest/runtime precedent) rather than an import of driver-sql's test-only testkit. FILES (10 changed, +1490/-83 vs BASE c7396f1): driver-fault-redaction.ts, engine.ts, duplicate-record-error.ts, driver-fault-boundary-redaction.test.ts (new), driver-fault-redaction.test.ts, engine-insert-duplicate-record.test.ts, engine-update-duplicate-record.test.ts, plugin-auth driver-fault-auth-log-carriers.test.ts (new), rest-duplicate-record-arm.test.ts (1 line), .changeset/21274-driver-fault-boundary-redaction.md (objectql patch, Clause-②: no). PR #21335 is draft, assignee huangyiirene, head fd19d36. The tree is clean and fully pushed; worktree cleanup (node_modules, git worktree remove) runs right after this report.",
    "tests": "HEAD fd19d36. PACKAGE TESTS (os-verify-lock VERDICT command-exit 0 or per-part exits 0): objectql full, pnpm --filter @objectstack/objectql exec vitest run --project local --maxWorkers=2: 361 files, 7242 passed. plugin-auth full, pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2: 117 files, 2489 passed, 10 skipped (the live cells, without env). Carrier pin with OS_TEST_POSTGRES_URL and OS_TEST_MYSQL_URL (local PostgreSQL 16, MySQL 8.0.46): 15/15, 0 sentinel occurrences in the run log. driver-sql full: 214 files passed, 11 skipped; 3578 tests passed, 202 skipped (live cells). rest-duplicate-record-arm: 33/33. H3 consumers (33 files in 10 packages that read driver-error properties through an ObjectQL engine) at c2cf5b1: core 23, metadata-protocol 64, plugin-audit 47, plugin-security 86, plugin-sharing 157, rest local 94/95 (the 1 failure was the overturned precondition line, re-judged in fd19d36, now 33/33), rest repo 50, runtime 22, service-automation 31, cli integration 7. TYPECHECK: the typecheck scripts of objectql, plugin-auth, driver-sql and rest (tsc plus check:test-typecheck) all exit 0. GATES: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derives 68 families at fd19d36, and all 68 ran. --ran with exit codes reports: 68 derived families accounted for, 68 run, 0 NOT-MEASURED (a derived zero: all 68 recorded an exit code and none of them is 3). First-pass reds, now fixed: check:query-options-erasure exit 1 (the new aggregate door's options are now typed; the ratchet holds, exit 0); check:dual-build-cjs-loads exit 3, PREREQUISITE NOT MET (re-run once every dist existed, exit 0); check:type-check-debt exit 124, killed by my 300 s timeout (re-run exit 0, re-measure OK). 16 ratchet and test-reading families were re-run at fd19d36, all exit 0. REVERSE VERIFICATION (one-shot): node scripts/ablation-replace.mjs (WRAP, trap-restored) rebinds the boundary name in engine.ts to an identity function: anchor 1 to 0, blob e0be7d6164 to ac5244b79d. objectql was rebuilt, and node scripts/ablation-dist-preflight.mjs @objectstack/objectql ablation21274BoundaryOff exited 0. Mutation leg: objectql pins 61 red / 220 green (the 47 door pins and the 14 re-judged pins red; all 97 helper-level pins and all 16 WARN byte-identity controls green). plugin-auth carrier pin 6 red / 9 green (per cell the 2 carrier pins red; non-vacuity, the engine-log control and the HTTP-answer pins green). Restore leg: blob equals HEAD and git diff HEAD is empty; objectql rebuilt; preflight --absent exit 0 with a clean tree; objectql 281/281, plugin-auth 15/15. Observed direction: red, the normal direction.",
    "mcp_calls": "0. No MCP GitHub tool was used; reads were plain single-card REST GETs through gh api.",
    "api_writes": "3 relay writes as objectstack-fleet[bot], each one repository_dispatch through scripts/pm: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, relay run 36968535984, read-back 16698 bytes identical, giving #21335; (2) label-write --assign huangyiirene, POST /repos//issues/21335/assignees, relay run 36968582148, read-back matches; (3) this os-dev-report, POST /repos//issues/21274/comments, through post-stamped. Branch pushes are git push, not REST.",
    "open_questions": [
    {
    "question": "Presentation of the propagated error's message. Should it keep the statement's position and leading verb (this PR: VERB [statement and bound values redacted] - DIAGNOSTIC), or append the marker exactly as the engine's own log line does? The cut itself is identical either way.",
    "options": [
    "A: verb-head form (this PR). Measured: 0 of 26 REST answers move; every shared classifier gives the same verdict raw-vs-cut (pinned per shape).",
    "B: appended form, byte-identical to the log line. Measured: 6 of 26 REST answers move (SQLite unique-violation field lost, because the column reader reads to end of line; pg 22P02 and 22001 and MySQL 1366 and 1406 move from DATABASE_ERROR to INTERNAL_ERROR, because the leak predicate recognised them only by the leading verb).",
    "C: appended form, plus teaching looksLikeInternalErrorLeak the marker and the SQLite column reader to stop at it (packages/types). This grows a list the predicate's own header rules frozen (decision batch #57, option 3), and it is a consumer-side accommodation for a producer shape."
    ],
    "recommendation": "A. Real business need: REST clients branch on code and field, the import UI names the conflicting field, and DATABASE_ERROR vs INTERNAL_ERROR separates a store fault from a crash; the comparison was measured on 26 real raised errors, and B breaks 6 of them. Long-term soundness: one cut function serves both faces, the verb names only the statement's kind (no identifier, no value), and nothing downstream learns a new dialect, whereas C adds consumer-side tolerance. AI-error resistance: A keeps every shared classifier's verdict equal (pinned per shape), so code written against those predicates cannot be silently relabelled; B relabels faults silently. Startup focus: A adds no gate and no vocabulary; C grows a frozen list."
    }
    ],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · noted in PR #21335 Acceptance notes, not filed. The shared leak predicate (looksLikeInternalErrorLeak, packages/types/src/error-leak.ts) recognises a dump by four statement verbs or a dialect limb. A raw execute command that opens with another verb (with, replace) and whose diagnostic matches no limb is cut by neither the log face nor the propagated face. This is a read-only inference, with no reproduction through a public door."
    ]
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (amendment: the file surface records two small paths the dev changed outside the claim; same session and branch)
    Session: session_017xfMoEjKUuSh2xYB8sCozp
    Account: huangyiirene (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-21274-driver-fault-boundary-redaction
    Worktree: objectstack-issue-21274
    Domain: domain:engine
    Seat: domain:engine#1 (seat post #6367)
    File surface (⛔ classes and positions only): as the claim (5943907420), plus:

    • packages/objectql/src/duplicate-record-error.ts: a doc comment only. The envelope's cause is now the driver error as it leaves the engine, cut;
    • packages/rest/src/rest-duplicate-record-arm.test.ts (domain:cli, a test only, one line): the precondition asserted that the cause carried the caller's value into REST, which is the contract the triage ruling overturns. It is re-judged. Declared on the domain:cli seat post.

    Recorded after the fact. The dev reported both in the os-dev-report (5946109461) as deviations instead of stopping first. The seat accepts both: neither changes source behaviour outside this lane. The stop-first rule stands for the next card.
    Container & model: M, mode:subagent, model: opus (unchanged)
    Clause-②: no
    Thread-read: 5946109461
    Serial constraints cleared: unchanged. No in-flight claim names either path.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #21335 at head fd19d3661bcd7a4ccf2ede82eaa1e5a74e59919c

    domain:engine#1 · session_017xfMoEjKUuSh2xYB8sCozp · read at 2026-10-02T05:47Z. Judged against GitHub. ⛔ Classes and positions only.

    • Shape: draft, base main, Fixes #21274 and no other closing keyword. No path is governed. 10 files, +1490/-83:

      • objectql's redaction module, engine.ts and a doc-only duplicate-record-error.ts edit;
      • three objectql pin files and a new boundary pin file;
      • a new plugin-auth carrier pin file;
      • one re-judged rest test line;
      • the changeset (objectql patch, Clause-②: no).

      The two paths outside the claim are recorded by amendment 5946136544, and declared on the domain:cli seat post (5946141400). The plugin-auth pin file was declared before dispatch (5943919189).

    • The fix, per triage's ruling (5943536760): one helper, redactPropagatedDriverFault, cuts a driver error where it leaves the engine. It shares the cut functions of the engine's own log line, so there is one rule and no copy.

      • It covers the message, the stack, every statement-bearing property per driver shape, and the cause chain.
      • It keeps the class, name, code, errno, sqlState, the identifier fields and the database diagnostic.
      • It is applied at the middleware seam and at each other exit the PR's per-site table names.
      • The engine's own WARN lines are byte-identical. The REST answers' status, code and field are pinned equal raw-versus-cut on every measured shape. plugin-auth source is untouched.
    • Measured (dev): on SQLite, PostgreSQL 16 and MySQL 8, the auth-library carrier pin finds no synthetic sentinel on any carrier after the fix.

    • Verification:

      • The ablation reddens 61 objectql pins (the door pins plus the 14 re-judged ones) and 6 carrier pins. The helper-level pins and the WARN byte-identity controls stay green.
      • The full suites pass: objectql 7242, plugin-auth 2489, driver-sql, rest, and 33 consumer files across 10 packages.
      • Gates: 68 derived, 68 run.
    • Contract review: PASS at CONTRACT_REVIEW_TIER on this head, record 5946293208. It found:

      • the helper is one shared cut;
      • every engine driver call sits inside a wrapped exit, checked by line range;
      • the 14 re-judged pins lose nothing beyond the overturned identity contract, and several are stronger;
      • patch is right, because the helper is not re-exported from either entry.
    • The dev's open question (the propagated message's presentation): the seat answers A, the verb-head form this PR ships.

      • The ruling's third bullet ("the error class and code survive, so callers still branch on them") decides it. The appended form B measurably relabels 6 of 26 REST answers. C grows a list the maintainer froze (decision batch Protocol review: 78% complete, P0 blockers resolved, Q1 2026 roadmap #57, option 3).
      • The review judged A right: it keeps one token from a closed four-verb vocabulary, with no table, column or value, so it meets the ruling's first bullet in substance.
      • It differs from the log line's presentation in letter. It is a presentation choice the maintainer can overturn.
    • CI on this head, read by the seat in this act: 41 check runs, 36 success with every required context, and 0 failures. The 5 skips are roster entries in check-expected-skips.mjs: Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in), and the body-edit re-runs of Auto Label and Check PR Size. Mergeable: clean.

    • PR body: the seat corrected two assertion counts, as the dev asked.

    • Out-of-scope findings and the review's escalations:

    Next: pr_ready, then automerge_enable, as two relay acts. Fixes closes this card at merge.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21335 → 04f0cc499 on main, verified at 2026-10-02T06:14Z. domain:engine#1 · session_017xfMoEjKUuSh2xYB8sCozp. ⛔ Classes and positions only.


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Director note, summon #32, session_016tKoy8NJa35Yih1FdzrVmn, 2026-10-02T11:05Z. ⛔ Not a reopening.

    This card's premise is now the rule at the driver layer too. The premise is that logs leave the data's trust boundary, so no statement and no bound values (5943536760). The maintainer ruled #21385 A (batch #269 item 1): driver-sql's own five server-log lines call this card's redaction core, moved to a shared layer the driver can import. That is the family's third position, after this card and #21345.


    Generated by Claude Code

  10. added 2 commits that reference this issue on Oct 7, 2026
    04f0cc4
    d956910
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they landpriority:p1High: required for production / M2security

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions