# Secret scanning

> Page: https://docs.plotwork.dev/secret-scanning. Index of every page: https://docs.plotwork.dev/llms.txt

Plotwork scans every diagram's source for what looks like a credential, and warns before the diagram is published or committed. An organisation can go further and refuse a publish to its library that has one.

## TL;DR

| Question | Answer |
|---|---|
| What is found? | Cloud keys (AWS, Google Cloud, Azure), GitHub, Slack and Stripe tokens, JWTs, private keys, connection strings with a password, a random value assigned to a name such as `password` or `api_key`, and Plotwork's own API keys. |
| Who gets the warning? | Everyone, on every draw and check: `draw_diagram`, `validate_diagram`, the API, the command line, the GitHub Action and the portal's editor. A personal account is warned and never blocked. |
| Does it stop a drawing? | No. The warnings are W700 to W710; the picture is drawn as always. |
| Does it fail CI? | Yes, with `validate --strict` or the Action's `fail-on: warning`. A secret warning is not a style suggestion. |
| Is the secret repeated back? | Never. A warning names the kind and shows the first four characters and a mask, such as `ghp_********`, and the source line under a CLI message is starred out. |
| What can an organisation add? | **Secret scanning before publish** on its Settings tab: Warn (the default) or Block. Block refuses a publish to the library, an agent's auto-publish, an editor save and a public link. |
| Is it a guarantee? | No. Detection is pattern-based: it can miss a secret, and it can name a value that is not one. |

## 1. What is found

The scan reads the raw source line by line, so labels, notes, attributes, comments and pasted JSON are all covered. Each finding has a code, a line, a column, a kind and a hint.

| Code | Kind | What it looks for |
|---|---|---|
| `W700` | `aws-access-key`, `aws-secret-key` | An access key id (`AKIA`, `ASIA` and the other prefixes) or a 40-character secret assigned to `aws_secret_access_key` |
| `W700` | `gcp-api-key`, `gcp-service-account` | A Google API key (`AIza` and 35 characters), or the `private_key_id` of a pasted service account file |
| `W700` | `azure-storage-key`, `azure-sas-token` | `AccountKey=` or `SharedAccessKey=` in a connection string, or the `sig=` of a shared access signature beside its `sv=` |
| `W701` | `github-token` | `ghp_`, `gho_`, `ghu_`, `ghs_`, `ghr_` and `github_pat_` tokens of their exact length |
| `W702` | `slack-token`, `slack-webhook` | `xoxb-`, `xoxp-` and the other Slack tokens, and `hooks.slack.com` addresses |
| `W703` | `stripe-key` | `sk_live_`, `rk_live_` and `whsec_` |
| `W704` | `jwt` | Three base64url parts whose first decodes to a header with `alg` |
| `W705` | `private-key` | A PEM private key header, such as `-----BEGIN RSA PRIVATE KEY-----` or the OpenSSH, EC, DSA, PGP and encrypted forms |
| `W706` | `assigned-secret` | A random value of ten characters or more, with letters and digits, assigned to a name such as `password`, `secret`, `token`, `api_key` or `client_secret` |
| `W707` | `connection-string` | An address with a password in it, such as `postgres://app:<password>@db` |
| `W708` | `plotwork-key` | A Plotwork API key, `pw_` and 64 hexadecimal characters |
| `W709` | `private-ip` | A private IPv4 address. Only when an organisation asks; see [section 4](#4-private-addresses-and-internal-hostnames). |
| `W710` | `internal-host` | A hostname matching the organisation's patterns. Only when an organisation asks. |

### What is left alone

A diagram that documents where a key goes should not be flagged for it. These are never findings:

| Written as | Example |
|---|---|
| A placeholder | `<token>`, `YOUR_API_KEY`, `changeme`, `redacted` |
| A reference to a value kept elsewhere | `${DB_PASSWORD}`, `{{ secrets.DEPLOY_TOKEN }}`, `%API_KEY%` |
| A repeated or counting value | `xxxxxxxx`, `00000000`, `1234567890abcdef` |
| A vendor's published example | AWS's documentation key, the jwt.io sample token |
| A stand-in password in an address | `postgres://user:password@localhost` |
| A short or plain word assigned | `password=hunter2`, `secret=configured_in_vault` |

Private addresses and internal hostnames are left alone too, unless an organisation turns them on, because an architecture diagram usually means them.

## 2. The warning everyone gets

A finding is a warning, never an error, so a draw, a check and a render all go ahead:

```text
warning W701: This looks like a GitHub token (ghp_********); remove it before you publish or commit.
   3 | node ci service "CI ****************************************"
     |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
hint: Put a placeholder such as <github-token> or a reference such as ${NAME} in its place, and rotate the value if it was ever shared. The scan matches patterns, so it can miss a secret or name a value that is not one.
```

The command line stars out every secret on the line it shows, under any message, so a CI log never carries one. In CI, `plotwork validate --strict` and the Action's `fail-on: warning` fail on a secret warning; the style suggestions W204, W205 and W5xx still pass.

## 3. Block at the library (organisations)

On the organisation's Settings tab, **Secret scanning before publish** has two choices. The owner and the admins change it; every member can read it.

| Choice | A publish to the library with a finding |
|---|---|
| Warn, the default | Goes ahead. The person drawing still sees each warning. |
| Block, recommended | Is refused with `422 orgs.publish_blocked`, and nothing is stored. |

Block covers every way into the library: a publish from an agent, the API or the command line, "Agents publish every diagram they draw", a save in the portal's editor, and sharing a diagram at a public link, which checks the stored picture. The answer is data, not an exception: its `details.findings` lists every finding with its code, line, column, kind and masked value. An agent is told to remove each one and publish again, and the editor lists them under its bar. Block is the Security item **Block secrets in published diagrams** in Recommended settings.

A personal account is never blocked, whatever an organisation it belongs to chose.

## 4. Private addresses and internal hostnames

Under Block, an organisation can also find:

| Option | Finds | Default |
|---|---|---|
| Private IP addresses | Addresses in `10.0.0.0/8`, `172.16.0.0/12` and `192.168.0.0/16`, as W709 | Off |
| Internal hostnames | Names matching up to 20 patterns, as W710. `*` stands for one or more labels at the start, so `*.corp.example` finds `db.eu.corp.example`. | None |

Both count only under Block, and only for a publish to that organisation's library.

## 5. What the audit log records

| Event | When | Metadata |
|---|---|---|
| `orgs.diagram.publish_blocked` | A publish was refused | `via` (`publish`, `auto`, `edit` or `share`), `findings` (how many), `kinds` (each kind with its count, such as `github-token=2`) |
| `orgs.settings.secret_scanning` | The setting changed | `mode`, `privateIps`, and `hostPatterns` (how many) |

Neither ever holds a value, a masked value or a hostname pattern. On Enterprise both are sent to your SIEM like every other event; see [Send audit events and drift to your tools](https://docs.plotwork.dev/siem-delivery.md).

## 6. Not a guarantee

Detection is pattern-based. It finds credentials with a known shape and random values assigned to secret-like names, and it can miss a password that is a plain word, a key of a format it does not know, or a secret split across lines. It can also name a value that is not a secret. Treat it as a second pair of eyes, keep secrets out of diagram source in the first place, and rotate any value that was published.

## See also

| Doc | Why |
|---|---|
| [Diagnostic codes](https://docs.plotwork.dev/reference/language/diagnostics.md) | Every code, W700 to W710 included |
| [Organisations](https://docs.plotwork.dev/organisations.md) | The library, roles and the other Settings |
| [Audit log](https://docs.plotwork.dev/audit-log.md) | Reading and exporting the events |
| [Diagrams in CI](https://docs.plotwork.dev/diagrams-in-ci.md) | `fail-on: warning` and the checks a pull request runs |
| [Why SVG](https://docs.plotwork.dev/why-svg.md) | What a published picture carries, and what it never does |
