{"slug": "handling-a-leaked-credential-with-the-postman-plugin", "title": "Handling a leaked credential with the Postman plugin", "summary": "Postman's Secret Scanner flagged a plain-text bearer token for a `patients-service` in a saved request inside a team workspace, and a coding agent running Postman's plugin resolved the incident end to end without the Postman app being opened. The agent routed work through the Postman MCP server when the Postman CLI could not run — on a phone with only the Claude app — chaining `detectedSecretsQueries`, `getDetectedSecretsLocations` and `getTeamUser` to surface two active findings (a Bearer Token and a JSON Web Token, both detected Oct 21 at 16:27 UTC) and identify who leaked them. The plugin defaults to the Postman CLI and falls back to the MCP server only when the CLI cannot run or lacks the command, as with Secret Scanner, audit logs and collection roles in the versions tested.", "body_md": "# Handling a leaked credential with the Postman plugin\n\nThe email from [Postman Secret Scanner](https://learning.postman.com/docs/administration/managing-your-team/secret-scanner/overview) arrives in the evening. A bearer token for `patients-service` sits in plain text in a saved request, inside a team workspace. You’re not at your desk, there’s no terminal in reach, and the token can read patient records.\n\nI staged this incident on the Postman DevRel team, in a dedicated test workspace with a fake token, so I could run the whole response end to end. My coding agent, with the [Postman plugin](https://learning.postman.com/docs/postman-plugin/overview) installed, handled every step, from finding the leak to leaving an audit trail, and I never opened the Postman app. What interested me most was which door the agent used at each step: [the Postman CLI](https://learning.postman.com/docs/postman-cli/postman-cli-overview) or the [Postman MCP server](https://learning.postman.com/docs/postman-ai/mcp-servers/overview). The answer changes several times in one incident, and the reason it changes is what this post is about.\n\n## How the Postman plugin picks a door\n\nThe [Postman plugin](https://learning.postman.com/docs/postman-plugin/overview) gives coding agents a set of skills for mocking, testing, monitoring, and documenting APIs. It also ships two ways to reach Postman. The docs describe the routing in two sentences. The plugin “sends your agent’s requests to the Postman CLI whenever the Postman CLI can complete them.” It “falls back to the Postman MCP Server only for tasks the Postman CLI doesn’t support.”\n\nIn practice, that fallback covers two situations:\n\n- The Postman CLI can’t run. The plugin’s setup skill only routes to the MCP server after an attempt to install the Postman CLI actually fails, for example in a hosted session that can’t install binaries.\n- The Postman CLI can run, but it doesn’t have the command yet. Secret Scanner, audit logs, and collection roles all fall in this bucket in the versions I tested.\n\nInstall the plugin into every coding agent it detects on your machine:\n\n```\nnpx @postman/postman-plugin install\n```\n\nThe [install docs](https://learning.postman.com/docs/postman-plugin/install/overview) list the supported agents and their minimum versions.\n\nThe plugin configures the hosted MCP server for you. Here’s the configuration it registers in Claude Code:\n\n```\n{\n  \"mcpServers\": {\n    \"postman\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.postman.com/mcp\"\n    }\n  }\n}\n```\n\nThe `/mcp` path selects the server’s Full tool set, which includes the enterprise tools this incident needs. You authorize it once with OAuth: in Claude Code, run the `/mcp` slash command and select the Postman server. If you’d rather connect the server by hand, the [MCP server setup guide](https://learning.postman.com/docs/developer/postman-api/postman-mcp-server/set-up-postman-mcp-server) covers the remote, local, and Docker options.\n\n## The alert, from my phone\n\nWhen the alert came in, I was out with only my phone. No laptop, no terminal, only the Claude app. That rules out the Postman CLI, so the first round goes through the MCP server. I’d added the same remote server (`https://mcp.postman.com/mcp`) as a custom connector in Claude, so it was already there in the mobile app. I asked:\n\nPrompt\n\nSecret Scanner flagged a token in the devrel-blog-mcp-test workspace. Where does it appear, and who put it there?\n\nThe agent chained three MCP tools: `detectedSecretsQueries` to list the active findings in the workspace, `getDetectedSecretsLocations` to turn each finding into a place, and `getTeamUser` to turn the `leakedBy` user ID into a name. Here’s the answer it gave me:\n\nAgent\n\n**Two active findings in devrel-blog-mcp-test, both from the same leak.**\n\n| Type | Preview | Detected | \n|---|---|---|\n| Bearer Token | `Bearer eyJ******` | Oct 21, 16:27 UTC | \n| JSON Web Token | `eyJhbGciOi******` | Oct 21, 16:27 UTC | \n\n**Where:** the `Authorization` header of the **Get patient** request, in the **Patients Service** collection. It’s the only occurrence of each finding, and the request still exists.\n\n**Who:** Anthony Viard saved the request with the token in it.\n\n**Why two findings:** the scanner matched the same header twice, once on the `Bearer` prefix and once on the token’s JSON Web Token format. Both need to be resolved after the rotation, or the alert stays open.\n\nI only have the obfuscated previews and SHA-256 hashes, not the token itself, so I can’t tell which service issued it. The request calls `/api/patients/{{patient_id}}`, so the patients-service credential is the one to rotate.\n\nEvery fact in that answer comes from the tool results. The scanner matched the [JSON Web Token](https://datatracker.ietf.org/doc/html/rfc7519) shape and the `Bearer` prefix separately, and the detection landed 39 seconds after I saved the collection. Neither finding carries the secret in full, only an obfuscated preview and a hash, which matters when the conversation runs through a model provider you don’t control. The name came back as mine, the expected result for a staged leak and the one I’d want to know first in a real one.\n\nUnder the hood, each location result is a short record. This is the one that answers “where” and “who”:\n\n```\n{\n  \"resourceType\": \"request\",\n  \"location\": \"Headers\",\n  \"occurrences\": 1,\n  \"leakedBy\": 12345678,\n  \"detectedAt\": \"2026-10-21T16:27:31.000Z\",\n  \"isResourceDeleted\": false\n}\n```\n\nNothing about this round needed a shell.\n\n## The blast radius, from the terminal\n\nA leaked token can’t wait until morning, so I moved to my laptop as soon as I could. The chat had told me what leaked and where. Fixing it meant updating an environment and running tests, which is work the Postman CLI handles well. I opened Claude Code in the project folder linked to the workspace (`postman workspace create` records that link in `.postman/resources.yaml`), where the plugin finds the Postman CLI and uses it.\n\nBefore rotating anything, I wanted to know who else depends on this service:\n\nPrompt\n\nBefore we rotate the patients-service token, what depends on patients-service and how does each consumer authenticate to it?\n\nThe agent reached for [Postman Context Graph](https://learning.postman.com/docs/use/context-graph/get-started) through the Postman CLI:\n\n```\npostman context-graph ask \"What depends on patients-service, and how does each consumer authenticate to it?\" --wait\n```\n\nThe answer came back in 29 seconds. Trimmed:\n\n```\n**patients-service** has **32 downstream consumers** (services that depend on it).\n\n**patients-service requires:** `require_auth(scopes=[patients.read])` for the most\ncommonly called endpoint (`GET /api/patients/:patient_id`)\n\n1. **allergies-service** - authenticates with `require_auth(allergies.write)`\n2. **appointments-service** - authenticates with `require_auth(appointments.write)`\n...\n13. **erp-bridge-service** - authenticates with `require_auth(scopes=[patients.read])`\n...\n32. **vitals-service** - authenticates with `require_auth(vitals.write)`\n\n- All consumers call patients-service over **HTTP (synchronous)**, primarily\n  hitting the `GET /api/patients/:patient_id` endpoint\n```\n\nA count of 32 consumers is what makes a rotation scary. The graph also narrowed it down: the leaked token carries the `patients.read` scope, and `erp-bridge-service` is the consumer that authenticates with that exact scope. That’s the service whose configuration has to change in the same window as the rotation.\n\n`patients-service` comes from [healthcare-org-app](https://github.com/healthcare-org-app), a simulated healthcare platform the DevRel team maintains and ingests into its Context Graph. The [Context Graph CLI reference](https://learning.postman.com/docs/postman-cli/postman-cli-context-graph) covers `ask`, `status`, and `ingest`. Each ask counts against your team’s Context Graph quota, so one precise question beats five vague ones.\n\n## The rotation, with one prompt\n\nI issued a new token for `patients-service` on the service side. I didn’t paste it into the chat, a file, or an environment variable. Everything else, I handed to the agent in one prompt:\n\nPrompt\n\nI’ve issued a new patients-service token. Remove the hard-coded header from the Patients Service collection, store the new token as a vault secret for the Patients Service – shared environment (I’ll enter the value myself), prove the request works, and make the collection read-only for the team while we clean up. Don’t close the Secret Scanner findings until I confirm the old token is revoked.\n\nThe plugin’s skills turned that into six steps and picked a door for each one. One step needed me: the agent ran `postman secret create`, which opened a one-time form in my browser with a match code (`fuzzy-orchid-42`) so I could check it came from this command. I pasted the token there. Here’s the summary the agent gave me afterward:\n\nAgent\n\n**Rotation done. Findings still open, waiting on you.**\n\n| Step | How | Result | \n|---|---|---|\n| Replace the hard-coded `Authorization` header in**Get patient** | Postman MCP server ( `updateCollectionRequest` ) | Header is now `Bearer {{patients_service_token}}` | \n| Create a vault secret for the new token | the Postman CLI ( `postman secret create` ) | You entered the value in the browser form. I only received the secret ID | \n| Link `patients_service_token` in**Patients Service – shared** to that secret | the Postman CLI ( `postman environment var set --secret-id` ) | The variable now points to the vault, and its value reads as `********` | \n| Run **Patients Service** against that environment | the Postman CLI ( `postman collection run` ) | Vault secret resolved. 1 request, 200 OK in 193 ms, 2 of 2 assertions passed | \n| Make the collection read-only for the team | Postman MCP server ( `getCollectionRoles` , then`updateCollectionRoles` ) | Team: Viewer. You: Editor, kept | \n| Close the two Secret Scanner findings | Waiting for your confirmation | Bearer Token and JSON Web Token, both Active | \n\nI read the collection’s roles before changing them and sent your Editor role along with the team’s Viewer role. That call replaces the whole role set, so leaving you out would have removed your access.\n\nThe “How” column is the plugin’s routing rule in action. The Postman CLI edits requests in local collection files, but this collection only lives in the cloud workspace, so the header change went through the MCP server. The secret, the variable link, and the run went through the Postman CLI because it has those commands and a shell was available. Collection roles have no Postman CLI command, so that step went back to the MCP server. I never told the agent which tool to use.\n\nThe vault step is the part I’d copy into any rotation. The token went from my clipboard into [Postman Shared Vault](https://learning.postman.com/docs/sending-requests/postman-vault/postman-vault-secrets/) without passing through the agent’s context, the chat transcript, my shell history, or a plain-text environment value. That last one matters more than it looks: Secret Scanner checks the shared values of variables, so storing the new token as a plain value would have risked a fresh alert. With the vault reference, the scanner found nothing new. The agent did every step except handling the secret itself.\n\nThe run’s assertions live in the collection, so the agent didn’t write them either. In my test workspace, [Postman Echo](https://learning.postman.com/docs/developer/echo-api/) stands in for `patients-service` and echoes back the headers it receives, so the test checks exactly what went out:\n\n``` js\npm.test(\"request carried the rotated token\", () => {\n  const sent = pm.response.json().headers.authorization;\n  pm.expect(sent).to.eql(`Bearer ${pm.environment.get(\"patients_service_token\")}`);\n});\n```\n\nAgainst the real service, the check that matters most is that the old token gets a 401 Unauthorized and the new one gets a 200.\n\n### Close the alert\n\nOnce the old token was revoked on the service side, I told the agent:\n\nPrompt\n\nThe old token is revoked. Close the findings.\n\nThe agent answered:\n\nAgent\n\n**Both findings are marked Revoked.**\n\n- Bearer Token: Revoked\n- JSON Web Token: Revoked\n\nOne of the two calls timed out on my side, so I read the findings back before retrying. Both already showed Revoked, so I didn’t send the change again.\n\nThat last paragraph is the behavior I want from an agent on any write. The MCP server’s `updateDetectedSecretResolutions` tool also tells agents never to mark a finding Revoked unless the key has actually been rotated, which is why I kept that step behind my own confirmation. Resolved findings drop out of Secret Scanner’s default results, so the agent checked them with a query that includes resolved findings.\n\n## The paper trail\n\n[Audit logs](https://learning.postman.com/docs/administration/managing-your-team/audit-logs) don’t record collection or environment edits. They record team administration events: API keys, members, roles, single sign-on (SSO), and the lifecycle of Secret Scanner findings. That makes them the right place to close an incident:\n\nPrompt\n\nShow me the audit trail for this incident.\n\nThe agent queried the audit logs and summarized them:\n\nAgent\n\n**Secret Scanner events in devrel-blog-mcp-test, Oct 21 (UTC)**\n\n| Time | Event | By | \n|---|---|---|\n| 16:27:31 | Bearer Token detected | Postman (Secret Scanner) | \n| 16:27:31 | JSON Web Token detected | Postman (Secret Scanner) | \n| 16:33:14 | Bearer Token marked Revoked | Anthony Viard, through the Postman plugin for Claude Code | \n| 16:38:10 | JSON Web Token marked Revoked | Anthony Viard, through the Postman plugin for Claude Code | \n\nThe two revoke entries carry the user agent `postman-claude-code-plugin/2.0.1/postman-api-mcp-server/1.8.0 (toolset: full)`.\n\nThat user agent is my favorite detail in the whole incident. The audit trail records that the change came through the Postman plugin, through the MCP server, with the Full tool set. When someone asks in six weeks who closed this alert and how, the answer is in the log: me, through my agent.\n\nHeads up: audit logs need an Admin or Super Admin role. Before I had admin access, the same request came back with “You do not have permissions to view team Audit logs.” The agent can only see what your account can see.\n\n## Try it yourself\n\nYou need a Postman Enterprise plan for Secret Scanner and audit logs. Scanning internal workspaces also needs the Advanced Security Administration add-on. Context Graph needs a team with ingested sources.\n\n1. Install the plugin with `npx @postman/postman-plugin install` .\n2. Create a team workspace for the test. Keep it team-visible, not public: in public workspaces, Secret Scanner replaces detected secrets automatically, which spoils the exercise.\n3. Tell your team admins first. A detection emails Team Admins and Workspace Admins.\n4. Paste a sample token into a saved request header, wait for the alert, then hand the incident to your agent and watch which door it picks at each step.\n\nThen ask for something the Postman CLI doesn’t cover yet, such as reading your API Catalog or your audit logs, and see whether your agent switches to the MCP server on its own or needs a nudge.\n\nWhat I like about this setup is that the routing rule is boring in the best way. The Postman CLI comes first because it’s cheaper and easier to script. The MCP server picks up what the Postman CLI can’t run or doesn’t have yet. During an incident, that means fewer decisions for me, and the agent picks the right tool without being asked.", "url": "https://wpnews.pro/news/handling-a-leaked-credential-with-the-postman-plugin", "canonical_source": "https://blog.postman.com/handling-a-leaked-credential-with-the-postman-plugin/", "published_at": "2026-11-10 16:00:41+00:00", "updated_at": "2026-11-10 17:00:45.781719+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-tools", "developer-tools"], "entities": ["Postman", "Postman Secret Scanner", "Postman CLI", "Postman MCP server", "Postman plugin", "Claude Code", "Claude app", "patients-service"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/handling-a-leaked-credential-with-the-postman-plugin", "markdown": "https://wpnews.pro/news/handling-a-leaked-credential-with-the-postman-plugin.md", "text": "https://wpnews.pro/news/handling-a-leaked-credential-with-the-postman-plugin.txt", "jsonld": "https://wpnews.pro/news/handling-a-leaked-credential-with-the-postman-plugin.jsonld"}}