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
requireQuota takes an untyped quota key, so a mistyped key silently grants unlimited quota.
The defect
packages/web/src/server/guards/requireQuota.ts:18 declares the parameter as quotaKey: string, even though QuotaKey = 'projects.max' | 'collaborators.org.max' already exists in packages/shared/src/plans/types.ts:20. Quotas also carries an index signature [key: string]: number (plans/types.ts:36).
limit is undefined at runtime but typed number, because the index signature plus the absence of noUncheckedIndexedAccess hides it.
isUnlimitedQuota(undefined) is undefined === -1, so false.
used + requested > undefined is a NaN comparison, so false.
The if does not fire and the guard returns { ok: true }.
The quota is not enforced. It fails open.
Scope
Latent, not active: both call sites in org-projects.server.ts:63 and :321 pass correct literals today. requireEntitlement.ts:48 has the same shape but fails closed, so it is lower priority.
The as unknown as Record<string, number> on line 48 is noise -- the index signature already permits that access. Removing the index signature is what does the work.
Done when
requireQuota takes quotaKey: QuotaKey
Quotas has no [key: string]: number index signature
The cast on line 48 is gone
requireEntitlement gets the same treatment for consistency (entitlement: EntitlementKey, no index signature on Entitlements, cast on line 48 gone)
requireQuotatakes an untyped quota key, so a mistyped key silently grants unlimited quota.The defect
packages/web/src/server/guards/requireQuota.ts:18declares the parameter asquotaKey: string, even thoughQuotaKey = 'projects.max' | 'collaborators.org.max'already exists inpackages/shared/src/plans/types.ts:20.Quotasalso carries an index signature[key: string]: number(plans/types.ts:36).With a key that is not in
quotas:limitisundefinedat runtime but typednumber, because the index signature plus the absence ofnoUncheckedIndexedAccesshides it.isUnlimitedQuota(undefined)isundefined === -1, sofalse.used + requested > undefinedis aNaNcomparison, sofalse.ifdoes not fire and the guard returns{ ok: true }.The quota is not enforced. It fails open.
Scope
Latent, not active: both call sites in
org-projects.server.ts:63and:321pass correct literals today.requireEntitlement.ts:48has the same shape but fails closed, so it is lower priority.The
as unknown as Record<string, number>on line 48 is noise -- the index signature already permits that access. Removing the index signature is what does the work.Done when
requireQuotatakesquotaKey: QuotaKeyQuotashas no[key: string]: numberindex signaturerequireEntitlementgets the same treatment for consistency (entitlement: EntitlementKey, no index signature onEntitlements, cast on line 48 gone)requireQuotarejects a string that is not aQuotaKey, so the index signature cannot quietly come backEffort: ~30 minutes. No runtime behavior change for correct keys.
Part of #778 (TypeScript correctness target state and tracker).