fix(runtime): allow det accounts from meta tx#15812
Conversation
Allow creating `DeterministicStateInitAction` from a delegated action by fixing the receiver id check. It used to check against the outer receiver instead of the inner. As tests show, this is not actually exploitable. This is merely an incomplete feature, preventing anybody from using deterministic accounts from meta transactions. This is a protocol change, hence it is protocol version gated.
There was a problem hiding this comment.
Pull request overview
This PR fixes delegated (meta) transactions containing DeterministicStateInitAction by validating the receiver ID against the inner DelegateAction.receiver_id (instead of the outer transaction receiver), enabling deterministic account creation via meta transactions. The change is protocol-version gated via a new ProtocolFeature.
Changes:
- Fix delegated-action validation to use the delegate action’s receiver ID (protocol-gated).
- Add integration tests covering meta-tx deterministic account creation and pre-/post-fix behavior.
- Register the new protocol feature and document it in the changelog.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
test-loop-tests/src/tests/deterministic_account_id.rs |
Adds meta-transaction tests and a protocol-version-configurable TestEnv setup. |
runtime/runtime/src/action_validation.rs |
Protocol-gated fix to validate inner delegated actions against the correct receiver ID. |
core/primitives-core/src/version.rs |
Adds FixDelegatedDeterministicStateInit protocol feature and assigns it a protocol version. |
CHANGELOG.md |
Notes the protocol change enabling deterministic accounts via meta transactions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- improve test setup to be more protocol version independent - fix typo
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #15812 +/- ##
==========================================
- Coverage 69.87% 69.83% -0.05%
==========================================
Files 940 942 +2
Lines 199810 200312 +502
Branches 199810 200312 +502
==========================================
+ Hits 139622 139878 +256
- Misses 55516 55732 +216
- Partials 4672 4702 +30
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@jakmeier @jancionear @pugachAG I am the researcher who reported this bug privately through your official HackenProof bug bounty as report NEAP-432, with a working PoC and a live RPC reproduction, before this PR was opened. The fix that landed here is the fix to my report. Please do not pretend otherwise. The PR description currently reads "A mistake on my initial implementation, gone unnoticed until now." That phrasing is factually inaccurate. The bug had not "gone unnoticed." It had been privately disclosed through your official bug bounty, triaged by HackenProof, and forwarded to NEAR engineering. The verifiable timeline is on the platform record. The fix being merged here, including the new I want to be honest about how I read this, without making it personal. I invested a significant amount of time on this disclosure, the PoC, the live RPC reproduction, and the follow-up evidence. By NEAR's own engineering action in this very PR, the finding is real and worth a protocol-version-gated fix. By NEAR engineering's own code comment introduced here, it is "a bug." The NEAR security team has nonetheless closed NEAP-432 as "Informative" and refused payment, arguing that the bug is not security-impacting because there is no adversary. There is a basic principle that holds in every reputable bug bounty program: if the team applies a fix that came from a reporter's disclosure, the team pays the reporter. Otherwise, the project is taking the reporter's work without compensating it. From the outside, the combination of shipping the fix, reframing the discovery as internal, and declining to pay the reporter is difficult to interpret as anything other than abuse of the program and unjust enrichment of the project at my expense. I am stating that plainly because I think the team deserves to hear it plainly, not as an accusation against any one of you individually but as a description of what the pattern looks like to a reporter who acted in good faith. I came in through the official channel, I followed the rules, and what I have received in return is a fix shipped on my report and a closure that denies the report ever mattered. From my side, that is, in plain terms, abuse of my good faith and a scam against a reporter who did everything the program asked of him. The argument used to deny payment does not hold against the NEAR program's published scope on HackenProof, which lists DoS without hardfork as an in-scope category with a USD 10,000 minimum reward. An unintentional DoS is still a DoS, and the program scope does not require an adversary. The PR description states, in NEAR's own words, that the previous code is "preventing anybody from using deterministic accounts from meta transactions." That is network-wide denial of service against the entire user population for one of the program's listed primary integration paths. NEAR engineering does not consume protocol-version slots on non-issues. The classification by the NEAR security team is incompatible with the engineering treatment shown in this PR. I am asking two things publicly here:
A formal appeal has been filed through HackenProof platform mediation. If this is not corrected through that channel, the full case, including the disclosure timeline, the verbatim engineering acknowledgement in this PR, the closure message from the NEAR security team, and the appeal correspondence, will be published as a documented enforcement failure. That publication will contain no exploit-enabling material beyond what is already public in this merged PR. The decision of whether that publication becomes necessary is in your team's hands. I would much rather this be resolved cleanly. NEAR has shipped solid security fixes from external reports before and has paid those reports per the program. This case should not be the exception. Thanks. |
|
@loopghost, thanks for reaching out. Since we’re also connected on HackenProof, where you submitted the initial report, we’ll follow up with you there. Thank you again for your contribution and support for the project. |
|
Thank you @walnut-the-cat for the quick reply and for confirming the follow-up will continue on HackenProof. I really appreciate that. I have put a significant amount of time and care into this finding (the technical analysis, the PoC, the live RPC reproduction, and the follow-up evidence), and my intention going forward is to keep investing time in the NEAR codebase and to keep collaborating responsibly with your team through the proper channel. NEAR is a serious project and I am genuinely interested in contributing to it in the long term. I trust the NEAR team will arrive at a fair resolution on this. I will wait for your follow-up on HackenProof and I am available there for any clarification or additional material that helps the review. Thanks again, and have a good day. |
|
Thanks @walnut-the-cat, and @jakmeier as the author of the fix. I appreciate the courtesy, but I want to be straight about where this landed, on the record and in the open. Through the private channel, the decision was confirmed as final: this is acknowledged as a real, reproduced, fixed bug, my report and PoC were called competent and good-faith, and there is "no disagreement on the technical facts." And the payout is zero, classified Informative. So the position is that NEAR received a private disclosure, shipped my fix under a dedicated protocol feature with a 212-line regression test reconstructing exactly the attack I described, and the reporter gets nothing and was, until I raised it, not even credited. I have made my full technical and scope argument privately and I will not relitigate it line by line here. But I do want to say this where it is visible: a confirmed defect in production consensus-critical validation logic on a live L1, fixed by the vendor under a ProtocolFeature gate, is not an Informative non-issue, and I do not accept that framing. I understand you may prefer to keep this discussion private, and that is fine by me. I have sent an email to [email protected] laying out the case in full, and I would genuinely appreciate continuing the conversation there, directly with the engineers who wrote and shipped this fix rather than only through triage. You confirmed the bug. You wrote "this is a bug" into the committed code. I am asking to engage with your team directly on how this was handled. Two concrete things:
I came in through the proper channel, followed every rule, and did high-quality work that you used. I think that deserved a fair result, and I think the record should reflect what actually happened here. |
|
Hello @loopghost, Thanks for reaching out. Please understand that we use the triaging channel to interact with all vulnerability reporters in the same manner: objectively and without emotions involved. For that reason, we avoid involving individual team members in the direct handling of bug reports or security vulnerabilities. That said, we assure you that the author of the PR and the engineering team were involved throughout the entire decision-making process for this case. If you would like to continue the discussion, we suggest going through HackenProof rather than [email protected], as HackenProof is the official, public channel we support, and the same team members are involved there as well. If you have additional details or reproduction steps to support the claim that this is a security vulnerability, we will gladly review and process them accordingly. As promised, we will update the PR description to note you as the bug reporter. Please also note that confirming a bug or writing a regression test does not make it a security vulnerability. We write regression tests for any feature development or bug fix, regardless of its security implications. Once again, we appreciate your time and contribution. |
Allow creating
DeterministicStateInitActionfrom a delegated action by fixing the receiver id check. It used to check against the outer receiver instead of the inner. (A mistake on my initial implementation, gone unnoticed until now.)As tests show, this is not actually exploitable. This is merely an incomplete feature, preventing anybody from using deterministic accounts from meta transactions.
This is a protocol change, hence it is protocol version gated.
edit: Thank you @loopghost for finding and reporting this bug!