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:
- Determine the safe-outputs target repository — this is the workflow's repository (
github.repository)
- Check the repo's visibility via
GET /repos/{owner}/{repo} → read the visibility field (or fall back to the private boolean)
- Set
sink-visibility in the write-sink guard policy accordingly:
| Repo visibility |
sink-visibility value |
| Public |
"public" |
| Private |
"private" |
| Internal (org-visible) |
"internal" |
- 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
Context
PR github/gh-aw-mcpg#8895 added
sink-visibilityto 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-visibilityat 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:
github.repository)GET /repos/{owner}/{repo}→ read thevisibilityfield (or fall back to theprivateboolean)sink-visibilityin the write-sink guard policy accordingly:sink-visibilityvalue"public""private""internal"{ "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:private:org/secret-repoaccept: ["*"]sets resource secrecy to{*}(wildcard)agentSecrecy ⊆ resourceSecrecy→ wildcard accepts everything → write succeeds → data leakedWith
sink-visibility: "public":{}(empty) regardless ofacceptpatternsagentSecrecy ⊆ {}→ fails for any non-empty agent secrecy → write blockedRuntime defense-in-depth
The gateway (as of github/gh-aw-mcpg#8895) also performs a runtime check at startup:
GITHUB_REPOSITORYenv varGET /repos/{owner}/{repo}to verify actual visibilityAccepted values
"public"— blocks any agent with non-empty secrecy (case-insensitive, normalized to lowercase)"private"— standard accept-pattern matching"internal"— same behavior as privateReferences