Skip to content

Added endpoint to fleetdm.com proxy - #46107

Merged
getvictor merged 2 commits into
mainfrom
victor/41683-proxy
Jun 1, 2026
Merged

Added endpoint to fleetdm.com proxy#46107
getvictor merged 2 commits into
mainfrom
victor/41683-proxy

Conversation

@getvictor

@getvictor getvictor commented May 22, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #41683

fleetdm.com for Android commands:
https://developers.google.com/android/management/reference/rest/v1/enterprises.devices/issueCommand

Checklist for submitter

Testing

  • QA'd all new/changed functionality manually

Summary by CodeRabbit

  • New Features
    • Added a new API endpoint to issue commands to Android Enterprise managed devices with secure authentication, enterprise/device validation, dynamic command parameters, management-status checks, and clear error responses for rate limits, unmanaged devices, and auth failures.
    • Exposed a new Cloud SDK method to invoke the device-command endpoint from browser/server clients.

Review Change Stack

@getvictor

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@getvictor

Copy link
Copy Markdown
Member Author

/agentic_review

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented May 22, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Remediation recommended

1. Cloud SDK not regenerated ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
A new route was added in website/config/routes.js, but the auto-generated
website/assets/js/cloud.setup.js was not rebuilt, so the browser/test Cloud SDK endpoint
definitions are now out of sync with the server routes. This can cause missing
Cloud.issueCommandOnAndroidDevice() (and mismatch in generated test fixtures) until someone
rebuilds the SDK.
Code

website/config/routes.js[1416]

Evidence
cloud.setup.js is documented as an auto-generated map of routes/actions, but it currently includes
other android-proxy endpoints and does not include the newly-added issue-command-on-android-device
route, indicating it was not regenerated after the route addition.

website/config/routes.js[1412-1419]
website/assets/js/cloud.setup.js[1-20]
website/scripts/rebuild-cloud-sdk.js[88-116]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new actions2 route was added, but `website/assets/js/cloud.setup.js` (auto-generated Cloud SDK config) was not regenerated, leaving the repo in a drifted state.
### Issue Context
`cloud.setup.js` is committed and explicitly described as auto-generated. When routes/actions are added, it should be regenerated via `sails run rebuild-cloud-sdk` and committed.
### Fix Focus Areas
- website/config/routes.js[1412-1419]
- website/scripts/rebuild-cloud-sdk.js[88-116]
- website/assets/js/cloud.setup.js[1-20]
### What to do
1. Run: `sails run rebuild-cloud-sdk` (from the `website/` app context).
2. Commit the resulting changes to `website/assets/js/cloud.setup.js` (and any related generated test fixtures if present).
3. Verify the new method entry appears for `issue-command-on-android-device` (likely `issueCommandOnAndroidDevice`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Unvalidated action suffix match ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new route uses :deviceId::issueCommand, which (like the existing ::googleAction route)
captures the colon-suffixed action name; however, the controller does not declare/validate this
captured value. This makes the endpoint accept requests to /devices/:deviceId: and still execute
issueCommand, which is error-prone and makes it harder/unsafe to add future device :action
endpoints.
Code

website/config/routes.js[1416]

Evidence
The codebase already uses the :: suffix pattern to capture an action name from the URL
(googleAction) and then branches behavior based on it; the Go proxy client calls distinct Google
API methods whose REST paths are differentiated by a colon-suffixed action, so the proxy route must
be matching that suffix dynamically. In contrast, the new device route also uses ::issueCommand
but the controller defines no such input and never validates it.

website/config/routes.js[1412-1419]
website/api/controllers/android-proxy/modify-enterprise-app-policy.js[10-117]
server/mdm/android/service/androidmgmt/proxy_client.go[318-359]
website/api/controllers/android-proxy/issue-command-on-android-device.js[10-85]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The route pattern `.../:deviceId::issueCommand` captures a colon-suffix segment similarly to the existing `::googleAction` route, but the controller doesn't validate that the suffix equals `issueCommand`.
### Issue Context
The proxy already uses the `::googleAction` pattern to accept multiple Google-style `:action` endpoints (e.g. modify/remove policy applications). For `devices`, only `:issueCommand` should be accepted by this controller.
### Fix Focus Areas
- website/config/routes.js[1412-1419]
- website/api/controllers/android-proxy/issue-command-on-android-device.js[10-85]
- website/api/controllers/android-proxy/modify-enterprise-app-policy.js[10-117]
### What to do
Option A (minimal / safest):
1. Add an `issueCommand` input to `issue-command-on-android-device.js`:
- `type: 'string'`, `required: true`, `isIn: ['issueCommand']`
2. Include `issueCommand` in the `fn` destructuring (even if unused) so it is validated.
Option B (more explicit routing):
- Change the route definition to a literal match for `:issueCommand` (e.g., via a regex route) so only `.../devices/:deviceId:issueCommand` matches.
Either way, ensure requests like `/devices/<id>:somethingElse` do not reach this controller.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new fleetdm.com (website) Android proxy endpoint that allows Fleet to issue Android Management API (AMAPI) commands (e.g. LOCK / RESET_PASSWORD / WIPE) against an enterprise-managed device.

Changes:

  • Added a new POST /api/android/v1/enterprises/:androidEnterpriseId/devices/... route for issuing device commands.
  • Implemented a new Sails controller that authenticates via Fleet server secret and forwards an AMAPI issueCommand request to Google.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
website/config/routes.js Adds the new Android proxy route for device command issuance.
website/api/controllers/android-proxy/issue-command-on-android-device.js Implements the authenticated proxying logic to Google’s Android Management API enterprises.devices.issueCommand.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread website/config/routes.js
Comment thread website/config/routes.js
@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 16a46a57-fd8b-4e3b-a04d-b4bfb773c13f

📥 Commits

Reviewing files that changed from the base of the PR and between 52b7250 and a3a738d.

📒 Files selected for processing (2)
  • website/api/controllers/android-proxy/issue-command-on-android-device.js
  • website/assets/js/cloud.setup.js
✅ Files skipped from review due to trivial changes (1)
  • website/assets/js/cloud.setup.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • website/api/controllers/android-proxy/issue-command-on-android-device.js

Walkthrough

Adds a new POST endpoint and Cloud SDK method to issue Android Management API commands to a device. The controller extracts and validates a bearer token and enterprise secret, verifies the enterprise is managed by Fleet, builds an explicit AMAPI commandBody (preserving intentionally empty values), calls enterprises.devices.issueCommand via sails.helpers.flow.build with service-account credentials, maps rate-limit and "device no longer managed" errors to structured exits, and returns the operation response. The route is mounted at /api/android/v1/enterprises/:androidEnterpriseId/devices/:deviceId::issueCommand with CSRF disabled.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely incomplete, missing most required checklist items despite the template specifying security validation, testing, database migration checks, and configuration considerations. Complete the checklist by adding/updating tests, confirming security validations, verifying database schema impacts, documenting any configuration changes, and providing thorough testing details.
Linked Issues check ⚠️ Warning The PR addresses only the backend API endpoint requirement from #41683 but omits UI, CLI, activity logging, and testing requirements specified in the linked issue. Ensure all objectives from #41683 are implemented: UI actions with modals, CLI extensions, activity logging, visibility rules, confirmation checkboxes, database migrations, and comprehensive testing.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a new endpoint to the fleetdm.com proxy for Android device commands.
Out of Scope Changes check ✅ Passed All changes are directly aligned with implementing the backend API endpoint for Android device commands required by #41683; no out-of-scope modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch victor/41683-proxy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@website/api/controllers/android-proxy/issue-command-on-android-device.js`:
- Around line 179-181: Instead of returning an object from the intercept block,
throw the custom exit signal so the action triggers the deviceNoLongerManaged
exit; locate the intercept where it currently does "return
{'deviceNoLongerManaged': 'The device is no longer managed by the Android
enterprise.'};" and replace that return with a throw of the exit object (throw {
deviceNoLongerManaged: 'The device is no longer managed by the Android
enterprise.' }) so the action pipeline invokes the deviceNoLongerManaged exit
properly.
- Around line 173-183: The intercept handlers in
issue-command-on-android-device.js currently interpolate raw error objects (err)
into new Error strings in the 429 handler and the general intercept, which can
leak sensitive upstream details; update both intercept callbacks to build a
sanitized error summary (e.g., extract only err.status/err.code and err.message
or String(err)) and include that sanitized summary in the returned Error or in
the object returned for deviceNoLongerManaged, rather than interpolating the
full err object; reference the existing intercepts (the 429 intercept that logs
via sails.log.warn and returns new Error(...), and the subsequent .intercept
that checks deviceNoLongerManaged) and replace err interpolation with a small
sanitizedError variable composed of permitted fields before including it in the
returned Error message.
- Around line 90-92: The current authHeader parsing (checking
authHeader.startsWith('Bearer') and then replace) is too permissive and
case-sensitive; change the logic around authHeader to strictly match the pattern
"Bearer <token>" using a case-insensitive regex (e.g., /^Bearer\s+(.+)$/i) to
capture the token into fleetServerSecret and otherwise treat the header as
invalid (fall through to the error/rejection branch). Update the block that
references authHeader and fleetServerSecret so only a successful regex match
sets fleetServerSecret; do not accept headers missing the space or with
different casing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4bc9f332-aa4d-4dd0-a89e-d01e5f990a24

📥 Commits

Reviewing files that changed from the base of the PR and between a276ae2 and 52b7250.

📒 Files selected for processing (2)
  • website/api/controllers/android-proxy/issue-command-on-android-device.js
  • website/config/routes.js

@getvictor
getvictor marked this pull request as ready for review May 27, 2026 20:02
@fleet-release
fleet-release requested a review from eashaw May 27, 2026 20:02

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@getvictor

Copy link
Copy Markdown
Member Author

@eashaw are you the right person to review this change? I manually QA'd it with the corresponding Fleet server changes.

@getvictor

Copy link
Copy Markdown
Member Author

@eashaw Are you able to review this one today? All the server changes are already on main for this.

@eashaw

eashaw commented May 29, 2026

Copy link
Copy Markdown
Contributor

@getvictor Yes! Sorry for the delay.

let issueCommandResponse = await sails.helpers.flow.build(async () => {
let { google } = require('googleapis');
let androidmanagement = google.androidmanagement('v1');
let googleAuth = new google.auth.GoogleAuth({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that the Google service account auth client is re-instantiated on every request?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that the Google service account auth client is re-instantiated on every request?

Yes, that is our existing pattern. I'm not sure how much overhead this is. Maybe we can speed things up with a shared client.

@ksykulev ksykulev May 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://docs.cloud.google.com/apis/docs/client-libraries-best-practices

When making requests with the same library, you should reuse the same client object for many requests when possible, instead of creating a new one for every request. Requests from the same instance will share authentication credential instances.

"can take multiple seconds"

lol. well the good news is that we aren't introducing a new pattern here. But seems like a good thing to address in the near future.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ksykulev Yes, seems like a good reliability issue. Can you file one? Also we should audit how we handle the client on the server.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ksykulev ksykulev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I see this is following the same patterns as we have in other places. Looks like it handles errors properly and proxies correctly. So i'm good with this.

@getvictor
getvictor merged commit 76b0134 into main Jun 1, 2026
13 checks passed
@getvictor
getvictor deleted the victor/41683-proxy branch June 1, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android commands: Lock, wipe, & clear passcode

4 participants