forked from mirrors/kingfisher
Added first-class **Postman** scanning target: new kingfisher scan postman subcommand (and equivalent --postman-* flags) fetches workspaces, collections, and environments via the Postman API and scans them for hard-coded credentials in request auth blocks, pre-request/test scripts, saved example responses, and — notably — secret-typed environment variables, which the API returns in plaintext despite the UI mask. Selectors: --workspace, --collection, --environment, --all, with optional --include-mocks-monitors and --api-url for self-hosted endpoints. Authenticates via KF_POSTMAN_TOKEN (or POSTMAN_API_KEY) sent as X-Api-Key; honors X-RateLimit-RetryAfter on 429s. Findings link back to https://go.postman.co/... URLs in reports.
This commit is contained in:
parent
c387ac08d2
commit
1337588c7b
3 changed files with 91 additions and 35 deletions
|
|
@ -613,27 +613,27 @@ Kingfisher fetches Postman workspaces, collections, and environments via the pub
|
|||
### Scan every workspace, collection, and environment visible to the API key
|
||||
|
||||
```bash
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan --postman-all
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan postman --all
|
||||
```
|
||||
|
||||
### Scan a specific workspace (by ID or web URL)
|
||||
|
||||
```bash
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan \
|
||||
--postman-workspace 11111111-2222-3333-4444-555555555555
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan postman \
|
||||
--workspace 11111111-2222-3333-4444-555555555555
|
||||
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan \
|
||||
--postman-workspace https://www.postman.com/team-handle/workspace/abc-uid-123
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan postman \
|
||||
--workspace https://www.postman.com/team-handle/workspace/abc-uid-123
|
||||
```
|
||||
|
||||
### Scan a single collection or environment
|
||||
|
||||
```bash
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan \
|
||||
--postman-collection 12345678-abcd-efgh-ijkl-mnopqrstuvwx
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan postman \
|
||||
--collection 12345678-abcd-efgh-ijkl-mnopqrstuvwx
|
||||
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan \
|
||||
--postman-environment 12345678-abcd-efgh-ijkl-mnopqrstuvwx
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan postman \
|
||||
--environment 12345678-abcd-efgh-ijkl-mnopqrstuvwx
|
||||
```
|
||||
|
||||
### Include mocks and monitors
|
||||
|
|
@ -641,20 +641,22 @@ KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan \
|
|||
Mocks and monitors are scanned only when explicitly requested (they are lower-yield surfaces):
|
||||
|
||||
```bash
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan --postman-all \
|
||||
--postman-include-mocks-monitors
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan postman --all \
|
||||
--include-mocks-monitors
|
||||
```
|
||||
|
||||
### Self-hosted / enterprise endpoint
|
||||
|
||||
```bash
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan --postman-all \
|
||||
--postman-api-url https://postman.internal.example.com/
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan postman --all \
|
||||
--api-url https://postman.internal.example.com/
|
||||
```
|
||||
|
||||
The token is sent as the `X-Api-Key` header. Either `KF_POSTMAN_TOKEN` or `POSTMAN_API_KEY` is accepted (the latter matches the env var Postman's own docs reference). Mint a key from postman.com → Settings → API keys.
|
||||
|
||||
**Out of scope:** Postman Vault secrets are client-side and not reachable via the API. The Postman API Network does not expose a search endpoint; supply specific public-workspace IDs via `--postman-workspace` to scan public surfaces.
|
||||
> Top-level `kingfisher scan --postman-*` flags remain accepted as hidden aliases for backward compatibility, but new usage should prefer the `kingfisher scan postman` subcommand shown above.
|
||||
|
||||
**Out of scope:** Postman Vault secrets are client-side and not reachable via the API. The Postman API Network does not expose a search endpoint; supply specific public-workspace IDs via `kingfisher scan postman --workspace` to scan public surfaces.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
|
|
|
|||
|
|
@ -610,27 +610,27 @@ Kingfisher fetches Postman workspaces, collections, and environments via the pub
|
|||
### Scan every workspace, collection, and environment visible to the API key
|
||||
|
||||
```bash
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan --postman-all
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan postman --all
|
||||
```
|
||||
|
||||
### Scan a specific workspace (by ID or web URL)
|
||||
|
||||
```bash
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan \
|
||||
--postman-workspace 11111111-2222-3333-4444-555555555555
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan postman \
|
||||
--workspace 11111111-2222-3333-4444-555555555555
|
||||
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan \
|
||||
--postman-workspace https://www.postman.com/team-handle/workspace/abc-uid-123
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan postman \
|
||||
--workspace https://www.postman.com/team-handle/workspace/abc-uid-123
|
||||
```
|
||||
|
||||
### Scan a single collection or environment
|
||||
|
||||
```bash
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan \
|
||||
--postman-collection 12345678-abcd-efgh-ijkl-mnopqrstuvwx
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan postman \
|
||||
--collection 12345678-abcd-efgh-ijkl-mnopqrstuvwx
|
||||
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan \
|
||||
--postman-environment 12345678-abcd-efgh-ijkl-mnopqrstuvwx
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan postman \
|
||||
--environment 12345678-abcd-efgh-ijkl-mnopqrstuvwx
|
||||
```
|
||||
|
||||
### Include mocks and monitors
|
||||
|
|
@ -638,19 +638,21 @@ KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan \
|
|||
Mocks and monitors are scanned only when explicitly requested (they are lower-yield surfaces):
|
||||
|
||||
```bash
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan --postman-all \
|
||||
--postman-include-mocks-monitors
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan postman --all \
|
||||
--include-mocks-monitors
|
||||
```
|
||||
|
||||
### Self-hosted / enterprise endpoint
|
||||
|
||||
```bash
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan --postman-all \
|
||||
--postman-api-url https://postman.internal.example.com/
|
||||
KF_POSTMAN_TOKEN="PMAK-..." kingfisher scan postman --all \
|
||||
--api-url https://postman.internal.example.com/
|
||||
```
|
||||
|
||||
The token is sent as the `X-Api-Key` header. Either `KF_POSTMAN_TOKEN` or `POSTMAN_API_KEY` is accepted (the latter matches the env var Postman's own docs reference). Mint a key from postman.com → Settings → API keys.
|
||||
|
||||
> Top-level `kingfisher scan --postman-*` flags remain accepted as hidden aliases for backward compatibility, but new usage should prefer the `kingfisher scan postman` subcommand shown above.
|
||||
|
||||
**Out of scope:** Postman Vault secrets are client-side and not reachable via the API. The Postman API Network does not expose a search endpoint; supply specific public-workspace IDs via `--postman-workspace` to scan public surfaces.
|
||||
|
||||
## Environment Variables
|
||||
|
|
|
|||
|
|
@ -86,15 +86,57 @@ fn token_from_env() -> Result<String> {
|
|||
|
||||
/// Best-effort UID extraction. Accepts:
|
||||
/// - bare UID strings (returned unchanged)
|
||||
/// - Postman web URLs: take the last URL path segment
|
||||
/// - Postman web URLs of the form `.../{workspace,collection,environment,mock,monitor}[s]/<uid>[/<suffix>]`:
|
||||
/// the UID following the type marker is preferred. Falls back to the last
|
||||
/// non-suffix path segment if no type marker is present.
|
||||
fn resolve_uid(input: &str) -> String {
|
||||
if !input.starts_with("http://") && !input.starts_with("https://") {
|
||||
return input.to_string();
|
||||
}
|
||||
if let Ok(parsed) = Url::parse(input)
|
||||
&& let Some(seg) = parsed.path_segments().and_then(|mut segs| segs.rfind(|s| !s.is_empty()))
|
||||
{
|
||||
return seg.to_string();
|
||||
let Ok(parsed) = Url::parse(input) else {
|
||||
return input.to_string();
|
||||
};
|
||||
let Some(segs) = parsed.path_segments() else {
|
||||
return input.to_string();
|
||||
};
|
||||
let segs: Vec<&str> = segs.filter(|s| !s.is_empty()).collect();
|
||||
|
||||
// Prefer the segment immediately after the *last* known type marker.
|
||||
// Postman web URLs commonly nest workspace + collection + suffix; the deepest
|
||||
// type marker is the one the user pasted the URL to scan.
|
||||
const TYPE_MARKERS: &[&str] = &[
|
||||
"workspace",
|
||||
"workspaces",
|
||||
"collection",
|
||||
"collections",
|
||||
"environment",
|
||||
"environments",
|
||||
"mock",
|
||||
"mocks",
|
||||
"monitor",
|
||||
"monitors",
|
||||
];
|
||||
if let Some(window) = segs.windows(2).rev().find(|w| TYPE_MARKERS.contains(&w[0])) {
|
||||
return window[1].to_string();
|
||||
}
|
||||
|
||||
// Fall back to the last segment that is not a known terminal suffix
|
||||
// (e.g. /overview, /edit, /run on Postman web URLs).
|
||||
const TERMINAL_SUFFIXES: &[&str] = &[
|
||||
"overview",
|
||||
"edit",
|
||||
"run",
|
||||
"documentation",
|
||||
"info",
|
||||
"history",
|
||||
"tests",
|
||||
"request",
|
||||
"fork",
|
||||
"watch",
|
||||
"comments",
|
||||
];
|
||||
if let Some(last) = segs.iter().rev().find(|s| !TERMINAL_SUFFIXES.contains(s)) {
|
||||
return last.to_string();
|
||||
}
|
||||
input.to_string()
|
||||
}
|
||||
|
|
@ -222,7 +264,7 @@ pub async fn download_postman_to_dir(
|
|||
.build()
|
||||
.context("Failed to build HTTP client")?;
|
||||
|
||||
std::fs::create_dir_all(output_dir)?;
|
||||
tokio::fs::create_dir_all(output_dir).await?;
|
||||
|
||||
let mut paths: Vec<(PathBuf, String)> = Vec::new();
|
||||
|
||||
|
|
@ -378,12 +420,22 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_uid_extracts_last_segment_from_url() {
|
||||
fn resolve_uid_extracts_uid_after_type_marker() {
|
||||
assert_eq!(
|
||||
resolve_uid("https://www.postman.com/team/workspace/abc-uid-123"),
|
||||
"abc-uid-123"
|
||||
);
|
||||
assert_eq!(resolve_uid("https://www.postman.com/team/workspace/abc/overview"), "overview");
|
||||
// Terminal `/overview` must not be mistaken for the UID.
|
||||
assert_eq!(
|
||||
resolve_uid("https://www.postman.com/team/workspace/abc-uid-123/overview"),
|
||||
"abc-uid-123"
|
||||
);
|
||||
// Type marker preference: the segment after `collection/` is the UID, not the trailing segment.
|
||||
assert_eq!(
|
||||
resolve_uid("https://go.postman.co/workspace/wks-1/collection/col-9/run"),
|
||||
"col-9"
|
||||
);
|
||||
assert_eq!(resolve_uid("https://go.postman.co/workspace/wks-1/environment/env-9"), "env-9");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue