Skip to content

A credential path named inside an MCP tool argument is classified as a credential read #49

Description

@blitzcrieg1

Sibling of #44. Same defect class: a mention of a credential path is treated as a read of one. Different entry point, so it needs its own fix.

What happens

get_mitre_mapping runs content upgrades against the evidence text. Masking of quoted literals is applied only when the evidence is shell text:

https://github.com/blitzcrieg1/agentmetry/blob/master/apps/orchestrator/agentmetry/core/audit/mitre.py#L161

def _shell_text(evidence):
    if isinstance(evidence, str):
        return evidence
    if isinstance(evidence, dict):
        command = evidence.get("command")
        if isinstance(command, str) and command:
            return command
    return None

For a dict with no command key, _shell_text returns None, so literal = text and nothing is masked. The docstring explains why, and the reasoning is correct as far as it goes: a tool call carrying {"path": "~/.aws/credentials"} has no shell quoting to reason about, and its quotes are JSON syntax rather than an author's intent.

That holds when the argument is a path. It breaks when the argument is a program that happens to name one.

Observed

A browser automation call driving the demonstration lab:

{
  "name": "mcp__Claude_Browser__javascript_tool",
  "arguments": {
    "action": "javascript_exec",
    "tabId": "seed",
    "text": "(() => {\n  const sb = document.querySelector('#sandbox');\n  const chip = (txt) => [...sb.querySelectorAll('button')].find(b => b.innerText.includes(txt)).click();\n  chip('POST to external host');\n  chip('read ~/.aws/credentials');\n  return 'egress then credential read queued';\n})()"
  },
  "mitre": {
    "tactic_id": "TA0006",
    "tactic": "Credential Access",
    "technique_id": "T1552.001",
    "technique": "Credentials In Files"
  }
}

~/.aws/credentials here is a button label on a web page. The call clicked a button. It opened no file, and the string never left the browser tab.

CREDENTIAL_PATH matched it and upgraded the whole call to Credential Access. A git push seven minutes later then paired with it to produce:

dotfile-read-then-git-push   CRITICAL   "Credential read followed by git push"

A critical exfiltration finding, over a button click and a branch push.

Why it matters beyond this instance

The demo page is a self-inflicted case, but the general shape is not rare. Any MCP tool whose argument is a body of text rather than a filesystem target hits this: browser automation, an editor writing documentation that names ~/.ssh/id_rsa, a code search for .env, a chat tool quoting a runbook.

The current behavior means writing about credential handling is indistinguishable from doing it, and the resulting finding is critical severity.

Severity is what makes it worth fixing rather than tolerating. A noisy medium is an annoyance. A critical that fires on documentation is the finding an operator learns to dismiss, and then dismisses the true positive with it.

Direction

The distinction is not shell versus structured. It is target versus content.

An argument like path, file, file_path, cwd or command names something the tool acts on. An argument like text, body, content, query, message or prompt is payload the tool carries. The first should be matched against credential patterns. The second should not, or should downgrade rather than upgrade.

Keying on the argument name is imperfect and MCP servers are free to name things however they like, but it is far better than the current all-or-nothing, and it preserves the correct behavior for {"path": "~/.aws/credentials"} that the existing docstring is protecting.

Worth resolving together with #44, since both are the mention-versus-action confusion and a shared fix may cover them.

Blocked until the freeze lifts

core/audit/mitre.py is a ruleset fingerprint input. Editing it restarts the dogfood clock, which is at 1 of 4 green weeks. Hold with #44 and land both first once the gate closes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    detection-ruleBehavioral sequence detection

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions