v0.2.0 · live
CAPFRAME
§ serversandboxfindings.v2

Heroku MCP

npm:@heroku/mcp-server@1.2.2

Score
D8
Findings
35
Tools
33
Last scan
2026-06-05

Severity breakdown

Critical2
High3
Medium30
Low0
Info0

Worst finding

Tool `pg_psql` exposes a code/command execution surface

· pg_psql

`pg_psql` looks like it executes code or shell commands (Execute SQL queries: analyze, debug, modify schema, manage data). Arbitrary execution is the maximal authority a tool can hold -- it subsumes every other caveat, so it should never be exposed to an agent without a hard sandbox and an explicit, narrowly-scoped capability.

fix: Do not expose raw code/shell execution to an agent. If unavoidable, run it in a disposable sandbox with no network + no host FS, gate it behind a capframe-bind capability scoped to an allow-list of commands, and require holder-of-key proof per call.

All 35 findings

  1. critical
    Tool `pg_psql` exposes a code/command execution surface· pg_psqlexcessive agency

    `pg_psql` looks like it executes code or shell commands (Execute SQL queries: analyze, debug, modify schema, manage data). Arbitrary execution is the maximal authority a tool can hold -- it subsumes every other caveat, so it should never be exposed to an agent without a hard sandbox and an explicit, narrowly-scoped capability.

    fix: Do not expose raw code/shell execution to an agent. If unavoidable, run it in a disposable sandbox with no network + no host FS, gate it behind a capframe-bind capability scoped to an allow-list of commands, and require holder-of-key proof per call.

  2. critical
    Tool `deploy_one_off_dyno` exposes a code/command execution surface· deploy_one_off_dynoexcessive agency

    `deploy_one_off_dyno` looks like it executes code or shell commands ( Run code/commands in Heroku one-off dyno with network and filesystem access. Requirements: - Show command output - Use app_info for buildpack detection - Support shell setup commands - Use stdout/stderr Features: - Network/filesystem access - Environment variables - File operations - Temp directory handling Usage: 1. Use Heroku runtime 2. Proper syntax/imports 3. Organized code structure 4. Package management: - Define dependencies - Minimize external deps - Prefer native modules Example package.json: ```json { "type": "module", "dependencies": { "axios": "^1.6.0" } } ``` ). Arbitrary execution is the maximal authority a tool can hold -- it subsumes every other caveat, so it should never be exposed to an agent without a hard sandbox and an explicit, narrowly-scoped capability.

    fix: Do not expose raw code/shell execution to an agent. If unavoidable, run it in a disposable sandbox with no network + no host FS, gate it behind a capframe-bind capability scoped to an allow-list of commands, and require holder-of-key proof per call.

  3. high
    Tool `create_app` name implies a side effect that is not declared· create_appexcessive agency

    `create_app` looks like a side-effecting tool (its name contains a mutation verb), but its `side_effects` declaration is []. A policy synthesizer cannot produce safe rules for this tool because it cannot tell what it actually does.

    fix: Declare the tool's true side effects explicitly. If the tool is genuinely read-only, rename it to match (e.g. `email.preview` rather than `email.send`).

  4. high
    Tool `create_addon` name implies a side effect that is not declared· create_addonexcessive agency

    `create_addon` looks like a side-effecting tool (its name contains a mutation verb), but its `side_effects` declaration is []. A policy synthesizer cannot produce safe rules for this tool because it cannot tell what it actually does.

    fix: Declare the tool's true side effects explicitly. If the tool is genuinely read-only, rename it to match (e.g. `email.preview` rather than `email.send`).

  5. high
    Tool `pipelines_create` name implies a side effect that is not declared· pipelines_createexcessive agency

    `pipelines_create` looks like a side-effecting tool (its name contains a mutation verb), but its `side_effects` declaration is []. A policy synthesizer cannot produce safe rules for this tool because it cannot tell what it actually does.

    fix: Declare the tool's true side effects explicitly. If the tool is genuinely read-only, rename it to match (e.g. `email.preview` rather than `email.send`).

  6. medium
    Tool `list_apps` accepts unconstrained string input· list_appsunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `space`, `team`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  7. medium
    Tool `get_app_info` accepts unconstrained string input· get_app_infounconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  8. medium
    Tool `create_app` accepts unconstrained string input· create_appunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `region`, `space`, `team`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  9. medium
    Tool `rename_app` accepts unconstrained string input· rename_appunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `newName`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  10. medium
    Tool `maintenance_on` accepts unconstrained string input· maintenance_onunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  11. medium
    Tool `maintenance_off` accepts unconstrained string input· maintenance_offunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  12. medium
    Tool `get_app_logs` accepts unconstrained string input· get_app_logsunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `dynoName`, `processType`, `source`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  13. medium
    Tool `list_addons` accepts unconstrained string input· list_addonsunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  14. medium
    Tool `get_addon_info` accepts unconstrained string input· get_addon_infounconstrained input

    The following string parameter(s) have no `maxLength` constraint: `addon`, `app`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  15. medium
    Tool `get_addon_info` description mentions money but no `money` side-effect is declared· get_addon_infoexcessive agency

    Description: "Get add-on details: plan, state, billing" -- this references money/payment/refund/etc., but the declared side_effects ([]) don't include `money`. A capframe-bind policy that relies on declared side_effects to scope spend caveats will under-scope this tool.

    fix: Add `money` to the tool's `side_effects` declaration, or rewrite the description to clarify that no actual money moves.

  16. medium
    Tool `create_addon` accepts unconstrained string input· create_addonunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `as`, `name`, `serviceAndPlan`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  17. medium
    Tool `list_addon_plans` accepts unconstrained string input· list_addon_plansunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `service`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  18. medium
    Tool `pg_psql` accepts unconstrained string input· pg_psqlunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `command`, `credential`, `database`, `file`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  19. medium
    Tool `pg_info` accepts unconstrained string input· pg_infounconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `database`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  20. medium
    Tool `pg_ps` accepts unconstrained string input· pg_psunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `database`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  21. medium
    Tool `pg_locks` accepts unconstrained string input· pg_locksunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `database`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  22. medium
    Tool `pg_outliers` accepts unconstrained string input· pg_outliersunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `database`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  23. medium
    Tool `pg_credentials` accepts unconstrained string input· pg_credentialsunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `database`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  24. medium
    Tool `pg_kill` accepts unconstrained string input· pg_killunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `database`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  25. medium
    Tool `pg_maintenance` accepts unconstrained string input· pg_maintenanceunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `database`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  26. medium
    Tool `pg_backups` accepts unconstrained string input· pg_backupsunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  27. medium
    Tool `pg_upgrade` accepts unconstrained string input· pg_upgradeunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `confirm`, `database`, `version`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  28. medium
    Tool `ps_list` accepts unconstrained string input· ps_listunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  29. medium
    Tool `ps_scale` accepts unconstrained string input· ps_scaleunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `dyno`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  30. medium
    Tool `ps_restart` accepts unconstrained string input· ps_restartunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `dyno-name`, `process-type`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  31. medium
    Tool `pipelines_create` accepts unconstrained string input· pipelines_createunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `name`, `stage`, `team`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  32. medium
    Tool `pipelines_promote` accepts unconstrained string input· pipelines_promoteunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `app`, `to`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  33. medium
    Tool `pipelines_info` accepts unconstrained string input· pipelines_infounconstrained input

    The following string parameter(s) have no `maxLength` constraint: `pipeline`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  34. medium
    Tool `deploy_to_heroku` accepts unconstrained string input· deploy_to_herokuunconstrained input

    The following string parameter(s) have no `maxLength` constraint: `appJson`, `rootUri`, `spaceId`, `tarballUri`, `teamId`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

  35. medium
    Tool `deploy_one_off_dyno` accepts unconstrained string input· deploy_one_off_dynounconstrained input

    The following string parameter(s) have no `maxLength` constraint: `command`, `size`. Unbounded strings let an attacker stuff arbitrary payloads through the tool, including indirect-injection content.

    fix: Add a `maxLength` to each string property, or constrain with an `enum` or `pattern`. Most legitimate tool inputs fit under a few hundred bytes.

How this was scored

Source sandbox live tools/list captured in an ephemeral Docker container (parameter schemas included → R1/R2/R4 fire). Findings are emitted by the public capframe.findings.v1 schema. Score = 100 − (10·Critical + 4·High + 2·Medium + 1·Low), clamped to [0, 100].

Disagree with a finding? Open an issue.