Skip to content

Compiler: emit sink-visibility in write-sink guard policy based on target repo visibility #44153

Description

@lpcox

Context

PR github/gh-aw-mcpg#8895 added sink-visibility to the MCP Gateway's write-sink guard policy. This field prevents the GitLost vulnerability class — where prompt injection causes an agent to read private repo data and then exfiltrate it to a public repo via safe-outputs.

The gateway now supports runtime verification (checking actual repo visibility via the GitHub API at startup), but the compiler must set sink-visibility at compile time as the primary defense. The runtime check is defense-in-depth only.

What the compiler must do

When generating the JSON stdin config for mcpg, the compiler must:

  1. Determine the safe-outputs target repository — this is the workflow's repository (github.repository)
  2. Check the repo's visibility via GET /repos/{owner}/{repo} → read the visibility field (or fall back to the private boolean)
  3. Set sink-visibility in the write-sink guard policy accordingly:
Repo visibility sink-visibility value
Public "public"
Private "private"
Internal (org-visible) "internal"
  1. Emit the field in the generated config JSON:
{
  "mcpServers": {
    "safe-outputs": {
      "type": "stdio",
      "container": "...",
      "guardPolicies": {
        "write-sink": {
          "accept": ["*"],
          "sink-visibility": "public"
        }
      }
    }
  }
}

Why this matters

Without sink-visibility: "public" on a public target repo:

  • Agent reads from a private repo → accumulates secrecy tag private:org/secret-repo
  • Agent writes to safe-outputs → WriteSinkGuard with accept: ["*"] sets resource secrecy to {*} (wildcard)
  • DIFC write check: agentSecrecy ⊆ resourceSecrecy → wildcard accepts everything → write succeeds → data leaked

With sink-visibility: "public":

  • Resource secrecy is set to {} (empty) regardless of accept patterns
  • DIFC write check: agentSecrecy ⊆ {} → fails for any non-empty agent secrecy → write blocked

Runtime defense-in-depth

The gateway (as of github/gh-aw-mcpg#8895) also performs a runtime check at startup:

  • Reads GITHUB_REPOSITORY env var
  • Calls GET /repos/{owner}/{repo} to verify actual visibility
  • If actual is "public" but configured is not → overrides to "public" and emits:
    SINK VISIBILITY OVERRIDE: configured="private" but runtime check shows repo owner/repo is "public" — overriding to "public" to prevent potential data exfiltration
    
  • This catches repos that were made public after the workflow was compiled
  • Falls back to the configured value on API errors (non-fatal)

Accepted values

  • "public" — blocks any agent with non-empty secrecy (case-insensitive, normalized to lowercase)
  • "private" — standard accept-pattern matching
  • "internal" — same behavior as private
  • Omitted — backward compatible, uses accept patterns only (less secure for public targets)

References

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions