Secret scanning
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. |
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:
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.
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 | Every code, W700 to W710 included |
| Organisations | The library, roles and the other Settings |
| Audit log | Reading and exporting the events |
| Diagrams in CI | fail-on: warning and the checks a pull request runs |
| Why SVG | What a published picture carries, and what it never does |