You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[finding] a bare-Error refusal reaching queryDataset is classified by WORDING — a security refusal whose text happens to contain "not registered" becomes a 200 with an empty chart #17335
⚠️Re-filed. The original card (#17130) was removed with the os-trump account when it was suspended, together with its dispatch comment and the os-dev-report. The delivered branch survived and is now PR #17336.
⛔ Correction to this body's first revision: it named the delivering PR as #17335, which is this card's own number. The PR is #17336.
Filed from PR #17125's out_of_scope_findings (card #16918), and verified on origin/main71629a151 rather than taken from the report.
The mechanism, measured
packages/services/service-analytics/src/analytics-service.ts wraps dataset execution and, on a throw, decides between re-raising and degrading to an empty result:
}catch(err){if(hasDeclaredErrorEnvelope(err))throwerr;// the escape hatchif(isMissingSourceError(err)){// else, judge by WORDING…// → a warn and a confident empty chart
hasDeclaredErrorEnvelope is true only when the error carries bothstatus: number and a non-empty code: string. A bare Error carries neither, so it falls through to:
functionisMissingSourceError(err: unknown): boolean{constraw=String((erras{message?: unknown})?.message??err??'');if(isMissingColumnOfRelation(raw))returnfalse;constmsg=raw.toLowerCase();return(msg.includes('no such table')||/relation\s+…\s+doesnotexist/i.test(raw)||msg.includes("doesn't exist")||msg.includes('not registered')||// ⇐msg.includes('unknown object')||// ⇐msg.includes('is not a registered object')// ⇐);}
⇒ Any bare Error whose message happens to contain one of those substrings is reclassified as "the source table is gone" and served to the caller as an empty chart — no exception, no 4xx, no 5xx. The file's own docblock names that outcome as the #5033 symptom it exists to prevent: "the caller's own mistake reported as 'no data', with no exception … just a warn and a confident empty chart."
⭐ What makes it security-shaped
PR #17125 (card #16918) makes the row-scope bridge refuse the query when a wired security service is broken — a throw new Error('[Analytics] row-level read scope could not be resolved for "X"; query refused (fail-closed).').
That is a bare Error. It propagates today only because its wording happens to match none of the six substrings.⚠️ ⛔ That is a coincidence, not a construction. The three underlined limbs are exactly the phrasings a registry or security refusal naturally reaches for. ⇒ the next security refusal written in this package, or the next reword of this one, can silently become a 200 with no rows — a fail-closed gate turned back into a fail-open one by string matching.
⛔ Not a defect in PR #17125. Its text was measured and today's behaviour is correct. What is defective is that the correctness rests on wording.
Fences
⛔ Relaxing a security boundary is the maintainer's floor. Every direction here tightens or is neutral. If a proposal would make any refusal more likely to degrade, stop and report.
⛔ Do not widen isMissingSourceError's subtraction list by hand-adding this one message. That fixes one string, not the property.
PR #17336 (branch claude/issue-17130-declared-envelope-on-analytics-refusals-r2, head d1cd397d4). Two legs:
A guard whose population is derived from source (TypeScript AST over every throw in the package), asserting no bare refusal this package raises can match isMissingSourceError.
readScopeUnresolvedError stamps the already-registeredREAD_SCOPE_COMPILE_FAILED / 500 on both row-scope resolution refusals, so they short-circuit at hasDeclaredErrorEnvelope instead of being sniffed. No ledger row added ⇒ Clause-②: no.
Filed from PR #17125's
out_of_scope_findings(card #16918), and verified onorigin/main71629a151rather than taken from the report.The mechanism, measured
packages/services/service-analytics/src/analytics-service.tswraps dataset execution and, on a throw, decides between re-raising and degrading to an empty result:hasDeclaredErrorEnvelopeis true only when the error carries bothstatus: numberand a non-emptycode: string. A bareErrorcarries neither, so it falls through to:⇒ Any bare
Errorwhose message happens to contain one of those substrings is reclassified as "the source table is gone" and served to the caller as an empty chart — no exception, no 4xx, no 5xx. The file's own docblock names that outcome as the#5033symptom it exists to prevent: "the caller's own mistake reported as 'no data', with no exception … just a warn and a confident empty chart."⭐ What makes it security-shaped
PR #17125 (card #16918) makes the row-scope bridge refuse the query when a wired security service is broken — a
throw new Error('[Analytics] row-level read scope could not be resolved for "X"; query refused (fail-closed).').That is a bare⚠️ ⛔ That is a coincidence, not a construction. The three underlined limbs are exactly the phrasings a registry or security refusal naturally reaches for. ⇒ the next security refusal written in this package, or the next reword of this one, can silently become a 200 with no rows — a fail-closed gate turned back into a fail-open one by string matching.
Error. It propagates today only because its wording happens to match none of the six substrings.⛔ Not a defect in PR #17125. Its text was measured and today's behaviour is correct. What is defective is that the correctness rests on wording.
Fences
isMissingSourceError's subtraction list by hand-adding this one message. That fixes one string, not the property.Delivered
PR #17336 (branch
claude/issue-17130-declared-envelope-on-analytics-refusals-r2, headd1cd397d4). Two legs:throwin the package), asserting no bare refusal this package raises can matchisMissingSourceError.readScopeUnresolvedErrorstamps the already-registeredREAD_SCOPE_COMPILE_FAILED/ 500 on both row-scope resolution refusals, so they short-circuit athasDeclaredErrorEnvelopeinstead of being sniffed. No ledger row added ⇒Clause-②: no.