-
Notifications
You must be signed in to change notification settings - Fork 0
π‘οΈ Sentinel: [CRITICAL] Fix Command Injection in CLI openBrowser #488
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: developmental
Are you sure you want to change the base?
Changes from all commits
f13f7e6
5697c18
1b8e30c
ca39f8e
227a005
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # deepmerge-ts stack exhaustion | ||
| # Forcing major version upgrade to 8.x introduces breaking changes in @prisma/config. Pinned to 7.1.6 instead. | ||
| GHSA-ggr8-5vv4-36mx | ||
| CVE-2026-40345 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,7 +34,8 @@ | |
| "undici": "^7.29.0", | ||
| "minimatch": "^10.0.0", | ||
| "@hono/node-server": "^2.0.5", | ||
| "body-parser": "^2.3.0" | ||
| "body-parser": "^2.3.0", | ||
| "deepmerge-ts": "7.1.6" | ||
|
Comment on lines
+37
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π Security & Privacy | π Major | ποΈ Heavy lift π§© Analysis chainπ Script executed: #!/bin/bash
set -e
printf '%s\n' '--- tracked files ---'
git ls-files | rg '(^|/)(package\.json|.*lock|osv-scanner\.toml)$' || true
printf '%s\n' '--- package.json ---'
cat -n package.json | sed -n '25,45p'
printf '%s\n' '--- osv-scanner.toml ---'
cat -n osv-scanner.toml | sed -n '1,20p'
printf '%s\n' '--- deepmerge-ts references ---'
rg -n -S 'deepmerge-ts|GHSA-ggr8-5vv4-36mx' --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' .
printf '%s\n' '--- dependency lock entries ---'
for f in $(git ls-files | rg '(^|/)(package-lock\.json|npm-shrinkwrap\.json|yarn\.lock|pnpm-lock\.yaml)$'); do
echo "### $f"
rg -n -C 3 'deepmerge-ts' "$f" || true
doneRepository: ContextualWisdomLab/argos Length of output: 3529 π Web query:
π‘ Result: The vulnerability GHSA-ggr8-5vv4-36mx, also identified as CVE-2026-40345, is a stack exhaustion issue in the deepmerge-ts library caused by uncontrolled recursion when merging recursive object graphs [1][2][3]. Affected Versions: All versions prior to 8.0.0 are affected (< 8.0.0) [4]. Fixed Version: The issue is fixed in version 8.0.0 [2][3][5]. Technical Detail: The vulnerability exists because the library's merge logic (affecting deepmerge, deepmergeCustom, deepmergeInto, and deepmergeIntoCustom APIs) fails to track visited objects or object pairs during recursive operations [6][3]. When provided with crafted input containing self-references at the same property path, the library recurses infinitely until it triggers a RangeError: Maximum call stack size exceeded, which can result in a synchronous crash or service interruption [6][2][7]. This cannot be triggered by plain JSON alone, as it requires the creation of a recursive object graph [6][3]. Citations:
π Script executed: #!/bin/bash
set -e
printf '%s\n' '--- root package manifest ---'
cat -n package.json | sed -n '1,70p'
printf '%s\n' '--- workspace manifests mentioning Prisma or deepmerge-ts ---'
rg -n -C 4 -S 'prisma|deepmerge-ts' --glob 'package.json' --glob '!node_modules/**' .
printf '%s\n' '--- source imports and calls ---'
rg -n -S 'from ["'\'']deepmerge-ts|require\(["'\'']deepmerge-ts|deepmergeCustom|deepmergeInto|deepmerge\(' --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' . || true
printf '%s\n' '--- lockfile importer and Prisma dependency context ---'
sed -n '1,40p' pnpm-lock.yaml
sed -n '5520,5545p' pnpm-lock.yamlRepository: ContextualWisdomLab/argos Length of output: 5250
π Affects 2 files
π€ Prompt for AI AgentsSource: MCP tools |
||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,7 @@ function openBrowser(url: string): void { | |
| // Command Injection λ°©μ§λ₯Ό μν΄ exec λμ spawn μ¬μ© | ||
| if (process.platform === 'win32') { | ||
| // Windows: cmd.exe λΉνΈμΈ start λͺ λ Ήμ΄ μ¬μ© | ||
| const child = spawn('cmd.exe', ['/c', 'start', '""', url.replace(/&/g, '^&')], { | ||
| const child = spawn('cmd.exe', ['/c', 'start', '""', url.replace(/([&|;<>()^])/g, '^$1')], { | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π¨ Windows escaping omits double-quote and percent
Was this helpful? React with π or π to provide feedback. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π Info: CLI caret-escape regex is correct The regex Was this helpful? React with π or π to provide feedback. |
||
| windowsVerbatimArguments: true, | ||
| detached: true, | ||
| stdio: 'ignore' | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π Info: Unused useEffect import left in refactored modals All five modals still import (Refers to this code) Was this helpful? React with π or π to provide feedback. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,17 +27,13 @@ export function CreateOrgModal({ open, onOpenChange }: CreateOrgModalProps) { | |
| const [errorMessage, setErrorMessage] = useState<string | null>(null) | ||
| const mutation = useCreateOrg() | ||
|
|
||
| useEffect(() => { | ||
| if (!open) { | ||
| const handleOpenChange = (next: boolean) => { | ||
| if (!next && mutation.isPending) return | ||
| if (!next) { | ||
| setName('') | ||
| setErrorMessage(null) | ||
| mutation.reset() | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [open]) | ||
|
|
||
| const handleOpenChange = (next: boolean) => { | ||
| if (!next && mutation.isPending) return | ||
| onOpenChange(next) | ||
| } | ||
|
Comment on lines
+30
to
38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π‘ Create-org form retains stale input after cancel The μ·¨μ button calls the parent Prompt for agentsWas this helpful? React with π or π to provide feedback. |
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,16 +28,12 @@ export function CreateProjectModal({ | |
| const [name, setName] = useState('') | ||
| const mutation = useCreateProject(orgSlug) | ||
|
|
||
| useEffect(() => { | ||
| if (!open) { | ||
| const handleOpenChange = (next: boolean) => { | ||
| if (!next && mutation.isPending) return | ||
| if (!next) { | ||
| setName('') | ||
| mutation.reset() | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [open]) | ||
|
|
||
| const handleOpenChange = (next: boolean) => { | ||
| if (!next && mutation.isPending) return | ||
| onOpenChange(next) | ||
| } | ||
|
Comment on lines
+31
to
38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π‘ Create-project form retains stale input after cancel The μ·¨μ button calls the parent Prompt for agentsWas this helpful? React with π or π to provide feedback. |
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,16 +32,12 @@ export function DeleteOrgModal({ | |
| const [confirmName, setConfirmName] = useState('') | ||
| const mutation = useDeleteOrg() | ||
|
|
||
| useEffect(() => { | ||
| if (!open) { | ||
| const handleOpenChange = (next: boolean) => { | ||
| if (!next && mutation.isPending) return | ||
| if (!next) { | ||
| setConfirmName('') | ||
| mutation.reset() | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [open]) | ||
|
|
||
| const handleOpenChange = (next: boolean) => { | ||
| if (!next && mutation.isPending) return | ||
| onOpenChange(next) | ||
| } | ||
|
Comment on lines
+35
to
42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π΄ Delete-org confirmation defeated after cancel Closing with the μ·¨μ button calls the parent Prompt for agentsWas this helpful? React with π or π to provide feedback. |
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,17 +28,11 @@ export function DeleteProjectModal({ | |
| const [confirmName, setConfirmName] = useState('') | ||
| const mutation = useDeleteProject() | ||
|
|
||
| useEffect(() => { | ||
| if (!project) { | ||
| setConfirmName('') | ||
| mutation.reset() | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [project]) | ||
|
|
||
| const handleOpenChange = (next: boolean) => { | ||
| if (next) return | ||
| if (mutation.isPending) return | ||
| setConfirmName('') | ||
| mutation.reset() | ||
| onClose() | ||
| } | ||
|
Comment on lines
31
to
37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π΄ Delete-project confirmation defeated after cancel The μ·¨μ button calls Prompt for agentsWas this helpful? React with π or π to provide feedback. |
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,19 +26,23 @@ export function RenameProjectModal({ | |
| const [name, setName] = useState('') | ||
| const mutation = useUpdateProject(project?.id ?? '') | ||
|
|
||
| useEffect(() => { | ||
| // project κ°μ²΄κ° λ°λ λ name μνλ₯Ό λκΈ°ννκΈ° μν΄ keyλ₯Ό μ¬μ©νλ λ°©μμΌλ‘ μ°ννκ±°λ, | ||
| // λͺ¨λ¬μ΄ μ΄λ¦΄ λ nameμ μ€μ νλλ‘ ν μλ μμ΅λλ€. | ||
| // μ¬κΈ°μλ keyλ₯Ό μ¬μ©νμ¬ νμ μνλ₯Ό κ°μ μ΄κΈ°ννκ±°λ Effectλ₯Ό μ¬μ©ν μ μμΌλ―λ‘, | ||
| // λ λλ§ μ€ μνλ₯Ό μ λ°μ΄νΈνλ λ°©μμ μ¬μ©ν©λλ€. | ||
| const [prevProject, setPrevProject] = useState(project) | ||
| if (project !== prevProject) { | ||
| setPrevProject(project) | ||
| if (project) { | ||
| setName(project.name) | ||
| } else { | ||
| setName('') | ||
| mutation.reset() | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [project]) | ||
| } | ||
|
Comment on lines
+33
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π Info: Rename modal render-sync relies on stable project reference The setState-during-render sync keyed on Was this helpful? React with π or π to provide feedback. |
||
|
|
||
| const handleOpenChange = (next: boolean) => { | ||
| if (next) return | ||
| if (mutation.isPending) return | ||
| setName('') | ||
| mutation.reset() | ||
| onClose() | ||
| } | ||
|
Comment on lines
41
to
47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π‘ Rename-project shows stale error on reopen The μ·¨μ button calls Prompt for agentsWas this helpful? React with π or π to provide feedback. |
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π Info: Unrelated pnpm override added
A
deepmerge-tsoverride pinned to7.1.6is added in package.json, unrelated to the CLI command-injection fix described by the PR.Was this helpful? React with π or π to provide feedback.