Skip to content

[SECURITY] - Fix four externally reachable security findings #175

Closed

Description

@davidindra

Greetings,

first of all, thank you for your amazing work on this project!

However, an external-perimeter security review of the server found four issues reachable from the internet without a foothold or an insider: two rated High, two Medium.

1. Emailed account links are built from the request Host header — CVSS 8.1

ForgotPassword.razor derives the reset link from NavigationManager.ToAbsoluteUri, which on a server-rendered page takes its base from the incoming request, and appsettings.json ships "AllowedHosts": "*", so a forged value is not rejected. Submitting the forgot-password form with a victim's email and an attacker-chosen Host produces a genuine email from the real server containing a valid reset token that points at the attacker's origin. The email-confirmation and email-change links are built the same way.

Preconditions: SMTP configured, the victim's address confirmed, no reverse proxy pinning the upstream Host, and the victim clicks.

2. A device with no stored public key can be taken over on the agent hub — CVSS 8.1

AgentHub carries no [Authorize], so hub methods are reachable anonymously. The deprecated unsigned UpdateDevice accepts any device whose stored PublicKey is empty, and UpdateDeviceSigned verifies the signature against the key the caller supplied in the same message when there is nothing stored to check against. Either path rewrites ConnectionId — the field that routes viewer commands — and adopts the caller's key permanently, so the takeover survives.

Preconditions: the device GUID and tenant GUID; with self-bootstrap on a single-tenant server, the tenant GUID is not needed. To measure exposure: SELECT count(*) FROM "Devices" WHERE "PublicKey" IS NULL OR "PublicKey" = '';

3. The self-bootstrap single-tenant restriction is bypassable — CVSS 6.5

The tenants.Count > 1 check runs only inside if (... && agentDto.TenantId == Guid.Empty). A caller who names an existing tenant GUID explicitly skips it, so "self-bootstrap is only allowed on single-tenant servers" is not enforced against a caller who declines to opt in.

Applies only when AllowAgentsToSelfBootstrap is enabled; it defaults to false.

4. Relay access tokens are compared with == — CVSS 6.8

SessionSignaler.ValidateToken uses an ordinary string comparison, which short-circuits on the first differing byte. The responder half of a relay session is unauthenticated (RequireAuthenticationForResponder is never set), so this token is the only check on that side.

Proposed fixes

A branch with one commit per issue is available at security-fixes (PR #174):

Commit
1 c231966 — adds AppOptions.PublicBaseUrl and an IPublicUrlProvider used by all five emailed-link sites
2 4fed504 — removes the unsigned UpdateDevice; restricts caller-supplied keys to bootstrapping a device the server has never seen
3 02ac81b — runs the single-tenant check whenever self-bootstrap is the authority, taking the tenant from the server
4 4c86806 — CryptographicOperations.FixedTimeEquals

Thanks in advance!

Activity

  1. bitbound commented on Sep 21, 2026

    @bitbound
    Owner

    Hey, David. Thanks for the report!

    Most of the AI findings are overstated and/or are "misunderstood" by the AI.

    1. Emailed account links are built from the request Host header — CVSS 8.1

    Verified. Will have this fixed in the next preview/release.

    1. A device with no stored public key can be taken over on the agent hub — CVSS 8.1

    Severity here is overstated. This would require an attacker to know the device ID (GUID) and tenant ID (GUID) of the device in order to "take over" it. A pre-existing compromise would already have had to happen (on the server or remote device) for an attacker to have those.

    That said, "security by random ID" is not security, which is why I implemented the signatures. (Interesting note: You'd be surprised how many closed-source commercial RMMs use the IDs for device authentication. Or at least did at some point.)

    The deprecated hub method needed to stay open for at least one release cycle. Otherwise, if a device failed to auto-update, it would be bricked with no way of reaching it.

    Given all that, a "taken over" device still has no authority to do anything on the server. It can't access data or do anything against other devices. By design, agents are always treated as untrusted and potentially compromised, even with signing. An attacker would be merely inviting you into their computer.

    Sure, the attacker could then try to impersonate the real user and try to get you to do something that would be compromising. But there should be end-user identification processes already in place. If there aren't, then there are bigger issues here.

    Given all of that, the viable attack surface here is very small (requiring existing compromises, outdated agents, social engineering, bad systemic security practices). The severity is much overstated.

    The deprecated method can be removed at this point, though, since the migration path has been released for a while now.

    The agent hub intentionally has no [Authorize] tag. Because they are not identities that participate in an authentication flow. Now that I have public/private keys for the agent, I could technically sign a header value and use that in an authentication handler. But I intend to add a "lobby area" for unauthenticated devices that fail signature verification for one reason or another, allowing you to inspect them and either force-uninstall the agent or (if it's a known, trusted device) rekey it and let it join. If I simply killed the request at the authentication handler middleware, that wouldn't be possible.

    1. The self-bootstrap single-tenant restriction is bypassable — CVSS 6.5

    This is a documentation issue. The self-bootstrapping agent feature was never intended for public use. I created it exclusively for automated testing scenarios (e.g. load testing). I'll move this to a new Developer config section like I did for the agent and update the comments.

    1. Relay access tokens are compared with == — CVSS 6.8

    Again, the "responder" connection doesn't participate in authentication. It's the desktop client that doesn't have an identity.

    I'll add a constant-time comparison for hygiene, but there's nothing to exploit here.

    • A timing attack isn't possible over a remote connection like this.
    • The access tokens and random session IDs are generated on-demand, short-lived, and are single-use. They exist only for milliseconds (maybe 1-2 seconds if the remote device is slow), in between the authenticated/authorized viewer clicking the connect button and the remote device responding. They are immediately consumed.
    • Even if a remote timing attack were technically possible, the random session ID would also have to be known.

    Also of note, this whole flow is likely to change in the release following the upcoming one, since I need to make changes for granular permissions within remote control sessions.

    Please let me know if you disagree with any of the above. Happy to discuss.

    Assuming you respond as a human and not AI, anyway.

    Not trying to be rude here. Hopefully you understand. The project would be wholly unsustainable if I'm talking to AI all day, spending 1,000x longer time responding to issues than it took the author to create them.

  2. davidindra commented on Sep 21, 2026

    @davidindra
    Author

    Hi Jared,
    thank you very much for your instantaneous response! Yes of course I am writing this message personally, and yes you are right that the findings were supported by AI. Also, I already filtered very much of the findings originally hinted by AI (as I was able to tell that these were overstated or otherwise irrelevant), and I appreciate you are taking the time to validate the remaining ones I mentioned, with your full knowledge as the sole project author.

    In regards to your reasoning about the found issues, I fully agree and respect your approach to handling those.

    In fact, the reason I took an more in-depth look into the project is that I would really like to start seriously using it for multiple users - and therefore I need to be sure that in regards of security, there is nothing significant missing. I am a many-years happy user of your previous Remotely project, and I want to keep up with the latest transformation of that project into ControlR, and would like to contribute if possible - in a ways you appreciate, not the ways that overwhelm you. I think that we really an open-source, sustainable, universal solution for PC remote control.

    Maybe, in the end, just a small opinion from a random man over the Internet 😉 It seems to me that you're thinking about the AI in general as a something that slows you down - however, in my experience, when used the right way, it's able to multiply the amount of work delivered - but it needs really good guidance, rules, gates etc. - so basically if you are able to describe your intents in a structured way, and describe the criteria of the final feature/application state and allow the agent to iterate through loops and self-testing the application, you are able to reach very satisfying results and make the human work more of the "thinking, planning" part, and less of the "manual, digging, boring" parts. Just wanted to tell from my personal experience... 🙂

    Wishing all the best! 🎉
    D.

  3. bitbound commented on Sep 23, 2026

    @bitbound
    Owner

    Thanks for the follow-up. 🙂

    I would really like to start seriously using it for multiple users - and therefore I need to be sure that in regards of security, there is nothing significant missing.

    That's totally understandable and expected.

    I am a many-years happy user of your previous Remotely project,

    Welcome to the new project! I'm happy to have you here, and I hope you find ControlR just as useful. Especially after I get some of the next big features in. 😉

    and would like to contribute if possible - in a ways you appreciate, not the ways that overwhelm you.

    I have a few things coming up that I'd appreciate some help with. The first is testers for the next preview version with Permissions V2 in it. The second is testers for the closed-beta of the Subscriber Portal, for which I'll soon be inviting people (https://portal.controlr.app). The third is reviewers for the ControlR docs site I'm going to publish soon.

    Once I'm done with changes like the permissions system rework (the kinds of changes that cause massive disruption across the whole codebase), I'd like to get to a point where I can start accepting external contributions. I need a few things in place before that can happen, though, such as a legally-binding CLA and a few trusted people who can do reviews.

    It seems to me that you're thinking about the AI in general as a something that slows you down

    I'm not sure what specifically gave this impression, but it's not exactly accurate. I do use AI for things, and I've been refining my processes and use cases a lot over the last year. I have a bot (screenshot below) that runs various AI-assisted automations on my private repos (code reviews, doc updates, etc.). I have another out-of-band automation using OCR (https://open-codereview.ai/) for reviews. I've even invested some of my savings into local AI hardware so I can run things overnight, test experiments without worrying about token usage, etc.

    I also use it for coding, but I haven't been very vocal about it. And that's mainly because it's very polarizing, and I've yet to see any discussions about AI use that capture the real nuances.

    Most discussions seem to focus on the how to use it safely/sustainably/effectively/etc. But from my experience, it's usually more important to think about where, when, and how often to use it. For low-risk tasks, or ones that can be verified accurate with 100% certainty, then yes, that's a prime target. For me, those kinds of tasks are UI, dev-ops related tasks, and some purely mechanical code changes that are both 100% covered in tests and easily verifiable visibly with low cognitive overhead.

    Or one-off side side projects. For example, I just had my self-hosted hardware build this overnight, because none of existing apps I tried worked the way I wanted on KDE: https://github.com/bitbound/WayType

    I also made image-editing MCP server (https://github.com/bitbound/image-edit-mcp), which has proven useful a few times. Though the primary motivation was for my wife's project. 😆

    However, when working in your "real" codebase, the risks can be more subtle. No matter how detailed you make your plans, you will miss edge cases that you would have caught if you were writing it yourself. You'll miss requirements. There are so many "discoveries" that happen in the process of writing something that simply doesn't surface in planning. This isn't new. It's always been that way in software development.

    So now the review process is your only line of defense to "notice" it. Unit tests rarely do, especially for things that are high-level concepts or flows that span multiple services/layers.

    Reviewing code is much harder when you didn't write it, so things are more likely to be missed in review.

    The other potential issue is loss of understanding of your own codebase. "Claude, write a plan for this. LGTM. Claude, implement plan." Do that enough, and pretty soon, you won't be able to form an accurate mental model of your codebase.

    I'm not sure what your background is, but from my experience, that mental model is critical. I could be sitting in a meeting, listening to someone describe an issue, and I can see the code flowing through services in my head. And often times, by the end, I'll have a pretty good idea of exactly where to look.

    There's a real danger of losing that. One might argue, "Well, you can just have AI find it for you." And yeah, I do use AI for debugging and pinpointing issues. But being able to see my own codebase in my head is not something I'm willing to give up as a developer.

    And this is all with one person working solo. If you throw a team into the mix, or in my upcoming case, a whole community, it changes dramatically. At least with a team at a company, I'm at least somewhat aware of everyone's technical ability when I'm reviewing code. That's not the case if I'm reviewing PRs from strangers. Every assumption that could be made when a technical person has thought through an issue and reviewed their own code... Well, all that gets thrown out the window.

    The few times I've talked publicly about being "slowed down" by AI, this is likely what I was referring to. If people are sending me code 5,000x faster than it takes me to review it, that's all I'll ever do. Apparently this is an issue with lots of open-source projects.

    Edit: The other thing I forgot to mention is that, I'm not even sure how people are claiming to ship 10x or whatever in a team setting. Everywhere I've worked in my career, code reviews have always been the bottleneck. Even before AI, code was getting generated faster than it could be reviewed. I've never asked any of my teammates, "Hey, why do you keep throwing up PRs when there's already a traffic jam of them waiting for review?" So I can only assume that it's some mixture of reasons that I personally feel: reviews are tedious, they're boring, and my eyes/brain glaze over after doing it for too long.

    Which brings me to the human/emotional aspect of it in my setting. As I've told another guy I was talked to, I have a tendency to be a "people-pleaser". It's hard for me to critique and push back on things, or outright say "no". So I'll spend hours wringing my hands and trying to craft a response that sounds polite. It's stressful for me. Afterward, I'll be exhausted and need a nap. 😆

    But seriously, I can't imagine yet how I'd handle increased popularity, where anyone with an idea can YOLO a PR at the repo in under 5 minutes. There needs to be some order to it, in a similar way that an internal team would plan work.

    So, put all of this together, and my official stance is "It's complicated. It's nuanced. It depends." 😆 And I'm still figuring out how to best use AI without giving up the things that are vital to me, or introducing risk into the project.

    I probably forgot some stuff, but I think that kinda conveys where my thought process is.

    Image
  4. bitbound commented on Sep 24, 2026

    @bitbound
    Owner

    @davidindra I forgot to mention. If you were interested in getting more involved, feel free to email or DM me anytime! :)

  5. bitbound commented on Oct 8, 2026

    @bitbound
    Owner

    Implemented in release v0.28.15.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions