DRAFT: contracts 0.10.0 upgrade — deny-path fix BLOCKED (verified.agent absent on success path) + upgrade breaks check:contracts - #199
Draft
andrei-hasna wants to merge 1 commit into
Conversation
…ssion for the deny-path prototype read
WIP / NOT LANDABLE AS-IS. Two blockers, both measured; see the PR body.
Carries:
- @hasna/contracts 0.5.2 -> 0.10.0
- the prototype-pollution regression test for the `key_record_mismatch`
deny path (tenant-auth.ts:113), which FAILS on this branch because the
fix is NOT here
Deliberately NOT carried:
- the deny-path fix. The briefed remedy was "read verified.agent instead
of verified.claims.agent". Measured against the installed 0.10.0
artefact, `agent` is on the FAILURE branch of ApiKeyVerifyResult only;
the ok:true branch is {ok, claims, kid, app, tid}. The brief's own stop
condition ("if it is absent after the upgrade, STOP and report rather
than hand-rolling a guard") therefore fires, so no guard was invented.
- the hasna.contract.json migration that 0.10.0 now forces. That is the
deployment-modes removal landing in this repo and it wants its own row.
Refs: 14471bf9, 65b71f2e
Agent: Polybius
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DRAFT — NOT LANDABLE AS-IS. Checks are red on purpose. Refs todos
14471bf9(upgrade lane) and65b71f2e(the defect).This lane was to upgrade
@hasna/contracts0.5.2 -> 0.10.0 and land the one-line deny-path fix. The upgrade is here. The fix is not, and the reason is a measured contradiction of the remedy itself.1. The briefed remedy does not exist on the success path
The instruction was to read
verified.agentinstead ofverified.claims.agentatsrc/lib/auth/tenant-auth.ts:113, with an explicit stop condition: confirm the field is present in the installed artefact; if it is absent, stop and report rather than hand-rolling a guard.Measured in the installed
node_modules/@hasna/contracts0.10.0 — not the changelog.agentis on the failure branch only:dist/auth/index.jscomputes exactly the right guard and attaches it only to denials:Line 113 sits after
if (!verified.ok) return ..., soverifiedis narrowed to the one branch with noagent.Two-sided runtime probe against the installed package:
The control arm reads the same field with the same expression where the type says it is present, and finds it — so "absent on success" is a fact about the package, not a broken probe.
The briefed one-liner would fail in both directions, and the second is the serious one:
verified.agentisundefined, soundefined !== row.principal_idis always true and every valid request becomeskey_record_mismatch.agentis not an own property of the success object either,verified.agentalso falls through toObject.prototype— last probe line above. Under the exact pollution this lane exists to close, the "fixed" expression returns the attacker's value and the deny still does not fire. It swaps one prototype-readable expression for another.So the stop condition fired and no guard was invented here. The real remedy is a decision for the defect's owner: either
@hasna/contractswires the existing line-237 guard into the success result (and into the principal at line 637, which a parallel census found still unguarded, plus five unguarded auditemitcalls at 598/611/620/629/641), or open-loops applies its ownObject.hasOwnguard at the call site — which is exactly what the brief told me not to do unilaterally.2. The upgrade is not additive — it breaks
check:contractsbun run typecheckbun test --timeout 120000(full)1151 pass, 57 skip, 3 fail— 1211 tests, 76 files, 553sbun run check:contractsFailures:
The third is the new regression test and is expected to fail here — the fix is absent. The first two are caused by the upgrade, established with a controlled pair: a second worktree at the same base sha
8691b69, differing only in the dependency version.Gate output:
This is the deployment-modes removal landing: 0.10.0 is the hotfix that makes a manifest carrying
deploymentModesfail validation.hasna.contract.jsonhere still has the old shape. The migration is mechanical — enum read from the installed artefact,SERVER_DATA_BACKENDS = ["sqlite","postgresql"]:deploymentModesstorage.mode: "local"->storage.backend: "sqlite"(keepsqlitePath; required when backend is sqlite)serviceSurfaces[0].deploymentModesI did not make that change. It is a semantic migration of a published contract manifest, governed by its own directive, and landing it inside a security lane whose fix is blocked would put an unreviewed vocabulary migration under a misleading title. It wants its own row.
What is in this branch
@hasna/contracts0.5.2 -> 0.10.0 (package.json,bun.lock)src/lib/auth/tenant-auth.test.ts: the pollution reproduction (currently failing — that is the open vulnerability) and an other-direction guard asserting a real mismatch still denies and a real match still allows, which already passes and would catch a fix that denied everythingThe reproduction was verified to fail before any change, on unmodified
main@8691b69:expect(decision.ok).toBe(false)/Expected: false, Received: true. A test that only passes afterwards would prove nothing here.Severity
Confirmed but not currently reachable in the loops verifier: no global prototype-pollution sink exists in that process and
bun auditreports no such advisory. This is a latent weakening of defence-in-depth, not a live break — priority should reflect that.Quarantine untouched; no exclude list edited.
@hasna/contracts@0.10.0installs cleanly through the arborist/bun path.Agent: Polybius
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.