Fix Issue #1428: Deny bash permission for Prometheus agent

- Change PROMETHEUS_PERMISSION bash from 'allow' to 'deny' to prevent unrestricted bash execution
- Prometheus is a read-only planner and should not execute bash commands
- The prometheus-md-only hook provides additional blocking as backup
This commit is contained in:
YeonGyu-Kim
2026-02-08 17:09:12 +09:00
parent 146ad3e2d9
commit 022dc167de
13 changed files with 736 additions and 57 deletions
@@ -0,0 +1,46 @@
import type { OpencodeClient } from "../constants"
import type { ConcurrencyManager } from "../concurrency"
import type { LaunchInput } from "../types"
import { log } from "../../../shared"
export async function createBackgroundSession(options: {
client: OpencodeClient
input: LaunchInput
parentDirectory: string
concurrencyManager: ConcurrencyManager
concurrencyKey: string
}): Promise<string> {
const { client, input, parentDirectory, concurrencyManager, concurrencyKey } = options
const body = {
parentID: input.parentSessionID,
title: `Background: ${input.description}`,
permission: [{ permission: "question", action: "deny" as const, pattern: "*" }],
}
const createResult = await client.session
.create({
body,
query: {
directory: parentDirectory,
},
})
.catch((error) => {
concurrencyManager.release(concurrencyKey)
throw error
})
if (createResult.error) {
concurrencyManager.release(concurrencyKey)
throw new Error(`Failed to create background session: ${createResult.error}`)
}
if (!createResult.data?.id) {
concurrencyManager.release(concurrencyKey)
throw new Error("Failed to create background session: API returned no session ID")
}
const sessionID = createResult.data.id
log("[background-agent] Background session created", { sessionID })
return sessionID
}
@@ -0,0 +1,7 @@
import type { LaunchInput } from "../types"
export function getConcurrencyKeyFromLaunchInput(input: LaunchInput): string {
return input.model
? `${input.model.providerID}/${input.model.modelID}`
: input.agent
}
@@ -0,0 +1,21 @@
import type { OpencodeClient } from "../constants"
import { log } from "../../../shared"
export async function resolveParentDirectory(options: {
client: OpencodeClient
parentSessionID: string
defaultDirectory: string
}): Promise<string> {
const { client, parentSessionID, defaultDirectory } = options
const parentSession = await client.session
.get({ path: { id: parentSessionID } })
.catch((error) => {
log(`[background-agent] Failed to get parent session: ${error}`)
return null
})
const parentDirectory = parentSession?.data?.directory ?? defaultDirectory
log(`[background-agent] Parent dir: ${parentSession?.data?.directory}, using: ${parentDirectory}`)
return parentDirectory
}
@@ -0,0 +1,39 @@
import type { OnSubagentSessionCreated } from "../constants"
import { TMUX_CALLBACK_DELAY_MS } from "../constants"
import { log } from "../../../shared"
import { isInsideTmux } from "../../../shared/tmux"
export async function maybeInvokeTmuxCallback(options: {
onSubagentSessionCreated?: OnSubagentSessionCreated
tmuxEnabled: boolean
sessionID: string
parentID: string
title: string
}): Promise<void> {
const { onSubagentSessionCreated, tmuxEnabled, sessionID, parentID, title } = options
log("[background-agent] tmux callback check", {
hasCallback: !!onSubagentSessionCreated,
tmuxEnabled,
isInsideTmux: isInsideTmux(),
sessionID,
parentID,
})
if (!onSubagentSessionCreated || !tmuxEnabled || !isInsideTmux()) {
log("[background-agent] SKIP tmux callback - conditions not met")
return
}
log("[background-agent] Invoking tmux callback NOW", { sessionID })
await onSubagentSessionCreated({
sessionID,
parentID,
title,
}).catch((error) => {
log("[background-agent] Failed to spawn tmux pane:", error)
})
log("[background-agent] tmux callback completed, waiting")
await new Promise<void>((resolve) => setTimeout(resolve, TMUX_CALLBACK_DELAY_MS))
}