Create MultiLinks using clipboard contents/then turn bundle into quicklink w/2 clicks.#27284
Create MultiLinks using clipboard contents/then turn bundle into quicklink w/2 clicks.#27284chucktaylor2024 wants to merge 3 commits intoraycast:mainfrom
Conversation
|
Congratulations on your new Raycast extension! 🚀 We're currently experiencing a high volume of incoming requests. As a result, the initial review may take up to 10-15 business days. Once the PR is approved and merged, the extension will be available on our Store. |
Greptile SummaryThis is a new extension that creates named groups of URLs from clipboard history and opens them all at once, with optional browser selection and Quicklink support. The core workflow is clean, but there is a shell injection vulnerability that must be fixed before merging.
Confidence Score: 2/5Not safe to merge — shell injection vulnerability requires a fix before this can be published A P0 shell injection issue affects two files and poses a real security risk to users. A P1 deeplink correctness issue and a missing metadata folder also need to be addressed before store publishing. src/open-group.tsx and src/list-groups.tsx both require switching from exec to execFile; a metadata/ folder with store screenshots needs to be added
|
| Filename | Overview |
|---|---|
| extensions/fast-multilinks/src/open-group.tsx | No-view command that opens group URLs via shell exec — has a shell injection vulnerability from unescaped URL/browser arguments passed to execAsync |
| extensions/fast-multilinks/src/list-groups.tsx | Main view for managing groups — same shell injection issue in openURL helper, plus hardcoded author slug in deeplink construction |
| extensions/fast-multilinks/src/create-group-from-clipboard.tsx | Clipboard-based group creation flow; contains the same hardcoded author slug in deeplink but no direct shell execution |
| extensions/fast-multilinks/package.json | Extension manifest with correct structure; @raycast/utils is listed as a dependency but never imported in source |
| extensions/fast-multilinks/eslint.config.js | Standard ESLint v9 flat config using defineConfig from eslint/config with @raycast/eslint-config — correct pattern |
| extensions/fast-multilinks/CHANGELOG.md | Initial changelog with correct {PR_MERGE_DATE} placeholder |
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/fast-multilinks/src/open-group.tsx
Line: 45-51
Comment:
**Shell injection via unescaped URL/browser arguments**
`execAsync` runs through `/bin/sh`, so any clipboard-sourced URL containing shell metacharacters (e.g. `https://evil.com" && rm -rf ~/Documents && echo "`) will be interpreted by the shell. An attacker who controls the clipboard (e.g. via a web page with clipboard API access) can execute arbitrary commands. Replace `exec` with `execFile` which spawns the process directly without a shell:
```typescript
import { execFile } from "child_process";
import { promisify } from "util";
const execFileAsync = promisify(execFile);
// with browser:
await execFileAsync("open", ["-a", group.browser, url]);
// without browser:
await execFileAsync("open", [url]);
```
The same issue exists in `list-groups.tsx`'s `openURL` helper (lines 35–41).
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: extensions/fast-multilinks/src/list-groups.tsx
Line: 35-41
Comment:
**Shell injection via unescaped URL/browser arguments**
Same issue as `open-group.tsx`: both `url` and `browserName` are interpolated into a shell string without escaping. Although `browserName` is constrained to dropdown values at entry time, the values are persisted to and read from `LocalStorage` as plain strings, so they are still attacker-influenced. Use `execFile` to avoid the shell entirely:
```typescript
import { execFile } from "child_process";
import { promisify } from "util";
const execFileAsync = promisify(execFile);
const openURL = async (url: string, browserName?: string) => {
if (browserName) {
await execFileAsync("open", ["-a", browserName, url]);
} else {
await execFileAsync("open", [url]);
}
};
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: extensions/fast-multilinks/src/list-groups.tsx
Line: 106
Comment:
**Hardcoded author slug in deeplink may not match published extension**
The deeplink hardcodes `Chucktaylor` as the author slug. Raycast typically normalises author identifiers to lowercase when resolving extension deep-links, so `raycast://extensions/Chucktaylor/...` may silently fail to open the `open-group` command after publishing. The same pattern appears in `create-group-from-clipboard.tsx` line 119. Verify the exact author slug used by Raycast for this extension (usually the lowercased `author` field) and update both files accordingly.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: extensions/fast-multilinks/package.json
Line: 44-46
Comment:
**Unused `@raycast/utils` dependency**
`@raycast/utils` is listed as a runtime dependency but is never imported in any file under `src/`. Remove it to avoid unnecessary bundle bloat.
```suggestion
"dependencies": {
"@raycast/api": "^1.104.12"
},
```
**Rule Used:** What: Every dependency listed in package.json must... ([source](https://app.greptile.com/review/custom-context?memory=bffc60eb-f9f2-4219-b804-76e29e267d43))
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "Initial commit: Fast MultiLinks extensio..." | Re-trigger Greptile
| for (const url of group.urls) { | ||
| if (group.browser) { | ||
| await execAsync(`open -a "${group.browser}" "${url}"`); | ||
| } else { | ||
| await execAsync(`open "${url}"`); | ||
| } | ||
| } |
There was a problem hiding this comment.
Shell injection via unescaped URL/browser arguments
execAsync runs through /bin/sh, so any clipboard-sourced URL containing shell metacharacters (e.g. https://evil.com" && rm -rf ~/Documents && echo ") will be interpreted by the shell. An attacker who controls the clipboard (e.g. via a web page with clipboard API access) can execute arbitrary commands. Replace exec with execFile which spawns the process directly without a shell:
import { execFile } from "child_process";
import { promisify } from "util";
const execFileAsync = promisify(execFile);
// with browser:
await execFileAsync("open", ["-a", group.browser, url]);
// without browser:
await execFileAsync("open", [url]);The same issue exists in list-groups.tsx's openURL helper (lines 35–41).
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/fast-multilinks/src/open-group.tsx
Line: 45-51
Comment:
**Shell injection via unescaped URL/browser arguments**
`execAsync` runs through `/bin/sh`, so any clipboard-sourced URL containing shell metacharacters (e.g. `https://evil.com" && rm -rf ~/Documents && echo "`) will be interpreted by the shell. An attacker who controls the clipboard (e.g. via a web page with clipboard API access) can execute arbitrary commands. Replace `exec` with `execFile` which spawns the process directly without a shell:
```typescript
import { execFile } from "child_process";
import { promisify } from "util";
const execFileAsync = promisify(execFile);
// with browser:
await execFileAsync("open", ["-a", group.browser, url]);
// without browser:
await execFileAsync("open", [url]);
```
The same issue exists in `list-groups.tsx`'s `openURL` helper (lines 35–41).
How can I resolve this? If you propose a fix, please make it concise.| const openURL = async (url: string, browserName?: string) => { | ||
| if (browserName) { | ||
| await execAsync(`open -a "${browserName}" "${url}"`); | ||
| } else { | ||
| await execAsync(`open "${url}"`); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Shell injection via unescaped URL/browser arguments
Same issue as open-group.tsx: both url and browserName are interpolated into a shell string without escaping. Although browserName is constrained to dropdown values at entry time, the values are persisted to and read from LocalStorage as plain strings, so they are still attacker-influenced. Use execFile to avoid the shell entirely:
import { execFile } from "child_process";
import { promisify } from "util";
const execFileAsync = promisify(execFile);
const openURL = async (url: string, browserName?: string) => {
if (browserName) {
await execFileAsync("open", ["-a", browserName, url]);
} else {
await execFileAsync("open", [url]);
}
};Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/fast-multilinks/src/list-groups.tsx
Line: 35-41
Comment:
**Shell injection via unescaped URL/browser arguments**
Same issue as `open-group.tsx`: both `url` and `browserName` are interpolated into a shell string without escaping. Although `browserName` is constrained to dropdown values at entry time, the values are persisted to and read from `LocalStorage` as plain strings, so they are still attacker-influenced. Use `execFile` to avoid the shell entirely:
```typescript
import { execFile } from "child_process";
import { promisify } from "util";
const execFileAsync = promisify(execFile);
const openURL = async (url: string, browserName?: string) => {
if (browserName) {
await execFileAsync("open", ["-a", browserName, url]);
} else {
await execFileAsync("open", [url]);
}
};
```
How can I resolve this? If you propose a fix, please make it concise.| }; | ||
|
|
||
| if (newlyCreatedGroup) { | ||
| const deeplink = `raycast://extensions/Chucktaylor/fast-multilinks/open-group?arguments=${encodeURIComponent(JSON.stringify({ groupName: newlyCreatedGroup.name }))}`; |
There was a problem hiding this comment.
Hardcoded author slug in deeplink may not match published extension
The deeplink hardcodes Chucktaylor as the author slug. Raycast typically normalises author identifiers to lowercase when resolving extension deep-links, so raycast://extensions/Chucktaylor/... may silently fail to open the open-group command after publishing. The same pattern appears in create-group-from-clipboard.tsx line 119. Verify the exact author slug used by Raycast for this extension (usually the lowercased author field) and update both files accordingly.
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/fast-multilinks/src/list-groups.tsx
Line: 106
Comment:
**Hardcoded author slug in deeplink may not match published extension**
The deeplink hardcodes `Chucktaylor` as the author slug. Raycast typically normalises author identifiers to lowercase when resolving extension deep-links, so `raycast://extensions/Chucktaylor/...` may silently fail to open the `open-group` command after publishing. The same pattern appears in `create-group-from-clipboard.tsx` line 119. Verify the exact author slug used by Raycast for this extension (usually the lowercased `author` field) and update both files accordingly.
How can I resolve this? If you propose a fix, please make it concise.| "@raycast/api": "^1.104.12", | ||
| "@raycast/utils": "^1.19.1" | ||
| }, |
There was a problem hiding this comment.
Unused
@raycast/utils dependency
@raycast/utils is listed as a runtime dependency but is never imported in any file under src/. Remove it to avoid unnecessary bundle bloat.
| "@raycast/api": "^1.104.12", | |
| "@raycast/utils": "^1.19.1" | |
| }, | |
| "dependencies": { | |
| "@raycast/api": "^1.104.12" | |
| }, |
Rule Used: What: Every dependency listed in package.json must... (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/fast-multilinks/package.json
Line: 44-46
Comment:
**Unused `@raycast/utils` dependency**
`@raycast/utils` is listed as a runtime dependency but is never imported in any file under `src/`. Remove it to avoid unnecessary bundle bloat.
```suggestion
"dependencies": {
"@raycast/api": "^1.104.12"
},
```
**Rule Used:** What: Every dependency listed in package.json must... ([source](https://app.greptile.com/review/custom-context?memory=bffc60eb-f9f2-4219-b804-76e29e267d43))
How can I resolve this? If you propose a fix, please make it concise.- Resize screenshots to 2000x1250 - Add store screenshots
|
This pull request has been automatically marked as stale because it did not have any recent activity. It will be closed if no further activity occurs in the next 7 days to keep our backlog clean 😊 |
Description:
Create multilinks using clipboard contents (up to 5 URLs at once), then turn the bundle into a QuickLink with 2 clicks.
Screencast
Checklist
npm run buildand tested this distribution build in Raycastassetsfolder are used by the extension itselfREADMEare placed outside of themetadatafolder