Add Rtools as a Windows FMA - #50028
Conversation
Start-Process -Wait waits for descendants as well as the process itself, so the install script never returned and the validator killed it at its 10-minute cap. Wait on the installer process alone, with a cap under that budget, and log progress plus registration state each poll so a slow unpack (a ~460 MB toolchain) is distinguishable from a stuck process.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #50028 +/- ##
==========================================
+ Coverage 67.97% 68.03% +0.06%
==========================================
Files 3922 3930 +8
Lines 250032 250268 +236
Branches 13334 13270 -64
==========================================
+ Hits 169949 170273 +324
+ Misses 64781 64691 -90
- Partials 15302 15304 +2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds Rtools as a Windows Fleet-maintained app (FMA), including the winget input + generated output manifest, plus a new Software page catalog icon and icon mapping entry.
Changes:
- Add Rtools winget input + install/uninstall PowerShell scripts.
- Add generated Windows output manifest and register the app in
apps.json. - Add Rtools catalog icon component and hook it into the software icon map.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/pages/SoftwarePage/components/icons/Rtools.tsx | Adds a new Rtools software catalog icon component. |
| frontend/pages/SoftwarePage/components/icons/index.ts | Registers the new icon in the icon map. |
| ee/maintained-apps/outputs/rtools/windows.json | Adds the generated Windows manifest for the Rtools maintained app. |
| ee/maintained-apps/outputs/apps.json | Registers Rtools in the maintained apps catalog output list. |
| ee/maintained-apps/inputs/winget/scripts/rtools_uninstall.ps1 | Adds an uninstall script for Rtools. |
| ee/maintained-apps/inputs/winget/scripts/rtools_install.ps1 | Adds an install script with bounded wait + registration polling. |
| ee/maintained-apps/inputs/winget/rtools.json | Adds the winget input definition for the Rtools Windows app. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $elapsed = 0 | ||
| while (-not $process.HasExited -and ($elapsed -lt $installTimeoutSeconds)) { | ||
| Start-Sleep -Seconds $pollSeconds | ||
| $elapsed += $pollSeconds | ||
| Write-Host "Installing... ($elapsed seconds, registered: $(Test-RtoolsRegistered))" | ||
| } | ||
|
|
||
| if (-not $process.HasExited) { | ||
| # The installer is still running at the cap. If Rtools has already registered | ||
| # in Add/Remove Programs the install itself finished and what is left is a | ||
| # lingering child, so stopping it is safe; otherwise the unpack genuinely did | ||
| # not finish in time and this is a real failure. | ||
| if (Test-RtoolsRegistered) { | ||
| Write-Host "Installer still running after ${installTimeoutSeconds}s but Rtools is registered; stopping the lingering process." | ||
| Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue | ||
| Start-Sleep -Seconds 2 | ||
| Exit 0 | ||
| } | ||
|
|
||
| Write-Host "Installer did not finish within ${installTimeoutSeconds}s and Rtools is not registered." | ||
| Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue | ||
| Exit 1 | ||
| } | ||
|
|
||
| $exitCode = $process.ExitCode |
There was a problem hiding this comment.
Good catch — fixed. After the parent exits, registration now gets a bounded 90s settle window instead of a single check, so a descendant still finishing the unpack can't produce a false failure.
That hand-off is exactly the behavior that makes Start-Process -Wait hang here, so it's a fair thing to expect on the exit path too. run 30372960989 passes.
…tion Addresses Copilot's review: Get-ItemProperty lacked -ErrorAction SilentlyContinue, so one unreadable registry key could abort the scan; and the install checked registration exactly once after the parent exited, which can lose the race when a descendant is still finishing the unpack -- the same hand-off that makes -Wait block. Registration now gets a bounded 90s settle window. Uninstall also requires the publisher, verified as 'The R Foundation' from the setup stub's PE version resource (Inno derives CompanyName from AppPublisher). That puts the exists-query value under test, since appExists matches on name only. Exit 0 when no entry is found, per repo convention.
Script Diff Resultsee/maintained-apps/outputs/rtools/windows.json=== Install // 92962266 -> 4679c4f2 ===
--- /tmp/old.R1TaBx 2026-07-28 15:23:02.856671427 +0000
+++ /tmp/new.RV7VWR 2026-07-28 15:23:02.856671427 +0000
@@ -58,6 +58,16 @@
$exitCode = $process.ExitCode
Write-Host "Install exit code: $exitCode"
+# The parent can exit while a descendant is still finishing the unpack and writing
+# the ARP entry -- the same hand-off that makes -Wait block. Give registration a
+# bounded window to appear rather than checking once.
+$settle = 0
+while (-not (Test-RtoolsRegistered) -and ($settle -lt 90)) {
+ Start-Sleep -Seconds $pollSeconds
+ $settle += $pollSeconds
+ Write-Host "Waiting for Rtools to register... ($settle seconds)"
+}
+
if (-not (Test-RtoolsRegistered)) {
Write-Host "Rtools did not register in Add/Remove Programs."
Exit 1
=== Uninstall // a289eb1d -> d5432a7e ===
--- /tmp/old.K14cEa 2026-07-28 15:23:02.880671602 +0000
+++ /tmp/new.z2qwxl 2026-07-28 15:23:02.880671602 +0000
@@ -1,5 +1,13 @@
+# The registry DisplayName carries a version and build ("Rtools 4.5 (6768-6492)"),
+# so match on a prefix. Require the publisher too, mirroring the manifest's exists
+# query. Verified as "The R Foundation" against the setup stub's PE version
+# resource (CompanyName, which Inno derives from AppPublisher). Requiring it here
+# puts the value under test -- the validator's appExists looks up by name only, so
+# a wrong exists-query publisher would otherwise ship undetected (cf. the Spyder
+# finding in #50016).
$softwareName = "Rtools"
-$softwareNameLike = "*$softwareName*"
+$softwareNameLike = "$softwareName*"
+$softwarePublisher = "The R Foundation"
$uninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
$machineKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
@@ -8,11 +16,11 @@
try {
[array]$uninstallKeys = Get-ChildItem -Path @($machineKey, $machineKey32on64) -ErrorAction SilentlyContinue |
- ForEach-Object { Get-ItemProperty $_.PSPath }
+ ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue }
$foundUninstaller = $false
foreach ($key in $uninstallKeys) {
- if ($key.DisplayName -like $softwareNameLike) {
+ if ($key.DisplayName -like $softwareNameLike -and $key.Publisher -eq $softwarePublisher) {
$foundUninstaller = $true
$uninstallCommand = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
if ($uninstallCommand -match '^\s*"([^"]+)"\s*(.*)$') {
@@ -27,7 +35,9 @@
$exitCode = $process.ExitCode; Write-Host "Uninstall exit code: $exitCode"; break
}
}
- if (-not $foundUninstaller) { Write-Host "Uninstaller for '$softwareName' not found."; Exit 1 }
+ # Nothing to remove is not a failure: uninstall scripts are idempotent here, as
+ # in nordpass_uninstall.ps1 and windsurf_uninstall.ps1.
+ if (-not $foundUninstaller) { Write-Host "Uninstall entry not found for '$softwareName'."; Exit 0 }
} catch { Write-Host "Error: $_"; Exit 1 }
Exit $exitCode |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
ee/maintained-apps/inputs/winget/scripts/rtools_install.ps1:23
- Test-RtoolsRegistered checks only DisplayName. That diverges from the app identity used elsewhere (exists query + uninstall script also require publisher), and can cause the timeout path to treat an unrelated/old "Rtools*" entry as proof of a successful install. Filter by Publisher to match the manifest identity.
$machineKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
$machineKey32on64 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
function Test-RtoolsRegistered {
$null -ne (Get-ChildItem -Path @($machineKey, $machineKey32on64) -ErrorAction SilentlyContinue |
ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |
Where-Object { $_.DisplayName -like "Rtools*" } |
Select-Object -First 1)
}
ee/maintained-apps/outputs/rtools/windows.json:19
- The embedded install script in refs (install_script_ref 4679c4f2) uses the same DisplayName-only registration check. If you tighten the check (e.g., require Publisher) in the source script, regenerate this output so the shipped manifest script stays consistent with the exists/uninstall identity.
"4679c4f2": "# Learn more about .exe install scripts:\n# http://fleetdm.com/learn-more-about/exe-install-scripts\n\n$exeFilePath = \"${env:INSTALLER_PATH}\"\n\n# Rtools uses Inno Setup and unpacks a large toolchain (a ~460 MB installer) to\n# C:\\rtools45, not Program Files. Two things made the previous script hang:\n# PowerShell's \"Start-Process -Wait\" waits for the process *and all of its\n# descendants*, and the unpack itself is slow. Wait on the installer process\n# alone, with a cap below the budget the caller allows, and log progress so a\n# slow unpack is distinguishable from a stuck one.\n$installTimeoutSeconds = 480\n$pollSeconds = 15\n\n$machineKey = 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n$machineKey32on64 = 'HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n\nfunction Test-RtoolsRegistered {\n $null -ne (Get-ChildItem -Path @($machineKey, $machineKey32on64) -ErrorAction SilentlyContinue |\n ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |\n Where-Object { $_.DisplayName -like \"Rtools*\" } |\n Select-Object -First 1)\n}\n\ntry {\n\n$process = Start-Process -FilePath \"$exeFilePath\" `\n -ArgumentList \"/VERYSILENT /SUPPRESSMSGBOXES /NORESTART\" `\n -PassThru\n# Touch .Handle so the exit code is still readable after the process ends:\n# Start-Process -PassThru otherwise returns $null for .ExitCode.\n$null = $process.Handle\n\n$elapsed = 0\nwhile (-not $process.HasExited -and ($elapsed -lt $installTimeoutSeconds)) {\n Start-Sleep -Seconds $pollSeconds\n $elapsed += $pollSeconds\n Write-Host \"Installing... ($elapsed seconds, registered: $(Test-RtoolsRegistered))\"\n}\n\nif (-not $process.HasExited) {\n # The installer is still running at the cap. If Rtools has already registered\n # in Add/Remove Programs the install itself finished and what is left is a\n # lingering child, so stopping it is safe; otherwise the unpack genuinely did\n # not finish in time and this is a real failure.\n if (Test-RtoolsRegistered) {\n Write-Host \"Installer still running after ${installTimeoutSeconds}s but Rtools is registered; stopping the lingering process.\"\n Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue\n Start-Sleep -Seconds 2\n Exit 0\n }\n\n Write-Host \"Installer did not finish within ${installTimeoutSeconds}s and Rtools is not registered.\"\n Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue\n Exit 1\n}\n\n$exitCode = $process.ExitCode\nWrite-Host \"Install exit code: $exitCode\"\n\n# The parent can exit while a descendant is still finishing the unpack and writing\n# the ARP entry -- the same hand-off that makes -Wait block. Give registration a\n# bounded window to appear rather than checking once.\n$settle = 0\nwhile (-not (Test-RtoolsRegistered) -and ($settle -lt 90)) {\n Start-Sleep -Seconds $pollSeconds\n $settle += $pollSeconds\n Write-Host \"Waiting for Rtools to register... ($settle seconds)\"\n}\n\nif (-not (Test-RtoolsRegistered)) {\n Write-Host \"Rtools did not register in Add/Remove Programs.\"\n Exit 1\n}\n\n# 3010 (reboot required) and 1641 (reboot initiated) are successful installs.\nif ($exitCode -eq 3010 -or $exitCode -eq 1641) { Exit 0 }\n\nExit $exitCode\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n",
WalkthroughAdds Windows support for Rtools 4.5.6768, including Winget metadata, silent installation and uninstallation scripts, registry-based detection, generated application records, installer checksum metadata, and a React icon mapped to the Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@ee/maintained-apps/inputs/winget/scripts/rtools_install.ps1`:
- Around line 18-23: Update Test-RtoolsRegistered in
ee/maintained-apps/inputs/winget/scripts/rtools_install.ps1 to use the target
version 4.5.6768 and require Publisher to match “The R Foundation” alongside
DisplayName and DisplayVersion in the shared predicate; parameterize or inject
the target version as appropriate. Regenerate the embedded install script in
ee/maintained-apps/outputs/rtools/windows.json at line 19 from the corrected
source; no separate logic change is needed there.
🪄 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 Plus
Run ID: a2116c2b-6104-4972-8e1b-940d1526e7b6
⛔ Files ignored due to path filters (1)
website/assets/images/app-icon-rtools-60x60@2x.pngis excluded by!**/*.png
📒 Files selected for processing (7)
ee/maintained-apps/inputs/winget/rtools.jsonee/maintained-apps/inputs/winget/scripts/rtools_install.ps1ee/maintained-apps/inputs/winget/scripts/rtools_uninstall.ps1ee/maintained-apps/outputs/apps.jsonee/maintained-apps/outputs/rtools/windows.jsonfrontend/pages/SoftwarePage/components/icons/Rtools.tsxfrontend/pages/SoftwarePage/components/icons/index.ts
| function Test-RtoolsRegistered { | ||
| $null -ne (Get-ChildItem -Path @($machineKey, $machineKey32on64) -ErrorAction SilentlyContinue | | ||
| ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } | | ||
| Where-Object { $_.DisplayName -like "Rtools*" } | | ||
| Select-Object -First 1) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Require the requested publisher and version before declaring installation complete.
Line 21 treats any Rtools* ARP entry as success. An existing older Rtools installation can therefore bypass both the settle loop and timeout failure path, reporting success while 4.5.6768 is not installed. Match Publisher and the target DisplayVersion in the shared predicate.
ee/maintained-apps/inputs/winget/scripts/rtools_install.ps1#L18-L23: parameterize or inject the target version and require it plusThe R FoundationinTest-RtoolsRegistered.ee/maintained-apps/outputs/rtools/windows.json#L19-L19: regenerate the embedded install script from the corrected source.
📍 Affects 2 files
ee/maintained-apps/inputs/winget/scripts/rtools_install.ps1#L18-L23(this comment)ee/maintained-apps/outputs/rtools/windows.json#L19-L19
🤖 Prompt for 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.
In `@ee/maintained-apps/inputs/winget/scripts/rtools_install.ps1` around lines 18
- 23, Update Test-RtoolsRegistered in
ee/maintained-apps/inputs/winget/scripts/rtools_install.ps1 to use the target
version 4.5.6768 and require Publisher to match “The R Foundation” alongside
DisplayName and DisplayVersion in the shared predicate; parameterize or inject
the target version as appropriate. Regenerate the embedded install script in
ee/maintained-apps/outputs/rtools/windows.json at line 19 from the corrected
source; no separate logic change is needed there.
Script Diff Resultsee/maintained-apps/outputs/rtools/windows.json=== Install // 4679c4f2 -> 38f938a7 ===
--- /tmp/old.E2lEdi 2026-07-28 17:45:02.785971077 +0000
+++ /tmp/new.ZRBp6l 2026-07-28 17:45:02.785971077 +0000
@@ -3,12 +3,9 @@
$exeFilePath = "${env:INSTALLER_PATH}"
-# Rtools uses Inno Setup and unpacks a large toolchain (a ~460 MB installer) to
-# C:\rtools45, not Program Files. Two things made the previous script hang:
-# PowerShell's "Start-Process -Wait" waits for the process *and all of its
-# descendants*, and the unpack itself is slow. Wait on the installer process
-# alone, with a cap below the budget the caller allows, and log progress so a
-# slow unpack is distinguishable from a stuck one.
+# Rtools unpacks a large toolchain (~460 MB) to C:\rtools45, not Program Files.
+# -Wait waits on descendants, so wait on the installer process alone and log
+# progress to tell a slow unpack apart from a stuck one.
$installTimeoutSeconds = 480
$pollSeconds = 15
@@ -27,8 +24,7 @@
$process = Start-Process -FilePath "$exeFilePath" `
-ArgumentList "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART" `
-PassThru
-# Touch .Handle so the exit code is still readable after the process ends:
-# Start-Process -PassThru otherwise returns $null for .ExitCode.
+# Keeps .ExitCode readable after the process ends.
$null = $process.Handle
$elapsed = 0
@@ -39,10 +35,7 @@
}
if (-not $process.HasExited) {
- # The installer is still running at the cap. If Rtools has already registered
- # in Add/Remove Programs the install itself finished and what is left is a
- # lingering child, so stopping it is safe; otherwise the unpack genuinely did
- # not finish in time and this is a real failure.
+ # Registered means the install finished and only a lingering child remains.
if (Test-RtoolsRegistered) {
Write-Host "Installer still running after ${installTimeoutSeconds}s but Rtools is registered; stopping the lingering process."
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
@@ -58,9 +51,7 @@
$exitCode = $process.ExitCode
Write-Host "Install exit code: $exitCode"
-# The parent can exit while a descendant is still finishing the unpack and writing
-# the ARP entry -- the same hand-off that makes -Wait block. Give registration a
-# bounded window to appear rather than checking once.
+# The parent can exit while a descendant is still writing the ARP entry.
$settle = 0
while (-not (Test-RtoolsRegistered) -and ($settle -lt 90)) {
Start-Sleep -Seconds $pollSeconds
=== Uninstall // d5432a7e -> d5ddfa34 ===
--- /tmp/old.x3c4s4 2026-07-28 17:45:02.807971020 +0000
+++ /tmp/new.ZpQuYC 2026-07-28 17:45:02.808971018 +0000
@@ -1,10 +1,5 @@
-# The registry DisplayName carries a version and build ("Rtools 4.5 (6768-6492)"),
-# so match on a prefix. Require the publisher too, mirroring the manifest's exists
-# query. Verified as "The R Foundation" against the setup stub's PE version
-# resource (CompanyName, which Inno derives from AppPublisher). Requiring it here
-# puts the value under test -- the validator's appExists looks up by name only, so
-# a wrong exists-query publisher would otherwise ship undetected (cf. the Spyder
-# finding in #50016).
+# The ARP DisplayName carries a version and build ("Rtools 4.5 (6768-6492)"), so
+# match on a prefix, plus the publisher to avoid other products sharing it.
$softwareName = "Rtools"
$softwareNameLike = "$softwareName*"
$softwarePublisher = "The R Foundation"
@@ -35,8 +30,6 @@
$exitCode = $process.ExitCode; Write-Host "Uninstall exit code: $exitCode"; break
}
}
- # Nothing to remove is not a failure: uninstall scripts are idempotent here, as
- # in nordpass_uninstall.ps1 and windsurf_uninstall.ps1.
if (-not $foundUninstaller) { Write-Host "Uninstall entry not found for '$softwareName'."; Exit 0 }
} catch { Write-Host "Error: $_"; Exit 1 } |
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** N/A # What this does Removes the **Captin** software icon: the `Captin.tsx` fallback icon component and its `SOFTWARE_NAME_TO_ICON_MAP` entry. Captin is deprecated and is being removed as a Fleet-maintained app. Its manifest fails the FMA validator because the download at the pinned URL now installs 2.0.1 while Homebrew still declares 1.3.1: ``` level=INFO msg="Looking for app: Captin, version: 1.3.1" app=Captin level=INFO msg="Found app: 'Captin' at /Applications/Captin.app, Version: 2.0.1, Bundled Version: 203" level=ERROR msg="App version '1.3.1' was not found by osquery" app=Captin ``` Split out of the FMA removal so the frontend change can be reviewed on its own. > [!NOTE] > **Merge order.** The FMA removal (input, output manifest, and `apps.json` entry) is in a > separate PR. Merging this one first leaves the Captin FMA without a fallback icon until > that PR lands, so it should merge after — or at the same time as — the FMA removal. Verified nothing else references `Captin` after the removal. The one remaining mention in the repo is a row in `cmd/osquery-perf/software-library/software.sql`, which is a load-test software inventory corpus rather than an FMA reference, so it is left alone. # Checklist for submitter - [x] QA'd all new/changed functionality manually No changes file: this is not a user-visible change on its own, and matches how other FMA catalog/icon PRs ship (e.g. #50028, #50024). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Removed the obsolete Captin icon from the software listings. * Prevented the retired icon from appearing in software name mappings. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Related issue: #50020
What this does
Adds Rtools as a Windows Fleet-maintained app. One of the 11 apps split out of #48501 that failed the FMA validator; #50016 shipped the 6 that passed.
Why it was failing
The install script hit the validator's 10-minute
executeScriptcap exactly:The locked installer in the temp dir shows a process was still alive.
Start-Process -Waitwaits for the process and all of its descendants, which is the same root cause as the other install-timeout apps in this batch.Rtools is also the one app in the batch where a slow unpack is a plausible second cause — the installer is ~460 MB and expands a full toolchain. So rather than assume, the script now waits on the installer process alone with a 480s cap (under the caller's 10-minute budget) and logs elapsed time plus Add/Remove Programs registration state on every poll. If the cap is reached:
Either way the CI log now says which one happened instead of just timing out.
Notes
CompanyName: The R Foundation,ProductName: Rtools. Inno derivesVersionInfoCompanyfromAppPublisher, so the ARP publisher isThe R Foundation— which is what the exists query uses.DisplayNameisRtools 4.5 (6768-6492), so the input usesfuzzy_match_nameand the exists query isname LIKE 'Rtools %'.C:\rtools45, not Program Files, so the validator's "no changes detected inC:\Program Files" line is an expected warning, not a failure.Checklist for submitter
SELECT *is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.Testing
All checks passed)apps.jsonis valid JSON with a description filled in.Summary by CodeRabbit