nosecretsv0.4.0

Showcase

Each example is a real nosecrets run: the files on the left, the session on the right. Open one to see how nosecrets behaves in the situation you are in.

Stop a leak

A token and a database password in one commit

site/captures/usecases/leaked-key/Detail: A token and a database password in one commit →
# .env
APP_ENV=production
DATABASE_URL=postgres://admin:Tr0ub4dor-and-3@db.internal:5432/app

# deploy.py
import requests

GITHUB_TOKEN = "ghp_R2kT9xLm4Qv7Bn3Zc8Wd5Hf1Jg6Ys0AaPeXq"

def release(version):
    return requests.post(
        "https://api.github.com/repos/acme/app/releases",
        headers={"Authorization": f"token {GITHUB_TOKEN}"},
        json={"tag_name": version},
    )
$ git add deploy.py .env
$ nosecrets scan --staged
.env:2:31 [HIGH] Postgres Connection URI (postgres-connection-uri) nsi_31013ade6422
  preview: Tr0...
deploy.py:3:17 [CRITICAL] GitHub Personal Access Token (github-pat) nsi_5e31772c2cb9
  preview: ghp_...PeXq
$ echo $?
1
  • scan --staged
  • github-pat
  • postgres-connection-uri
  • exit 1

Deleting the line afterwards does not help

site/captures/usecases/staged-snapshot/Detail: Deleting the line afterwards does not help →
# config.py
API_URL = "https://api.acme.example"
GITHUB_TOKEN = "ghp_R2kT9xLm4Qv7Bn3Zc8Wd5Hf1Jg6Ys0AaPeXq"
$ git add config.py
$ sed -i.bak '/GITHUB_TOKEN/d' config.py && rm config.py.bak
$ cat config.py
API_URL = "https://api.acme.example"
$ nosecrets scan --staged
config.py:2:17 [CRITICAL] GitHub Personal Access Token (github-pat) nsi_5e31772c2cb9
  preview: ghp_...PeXq
  • scan --staged
  • git index

Adopt it

Adopt it in a repository that already exists

site/captures/usecases/adopt/Detail: Adopt it in a repository that already exists →
# legacy/import_users.py
# One-off migration script from 2021.
GITHUB_TOKEN = "ghp_R2kT9xLm4Qv7Bn3Zc8Wd5Hf1Jg6Ys0AaPeXq"

# src/app.py
import os

TOKEN = os.getenv("GITHUB_TOKEN")
$ nosecrets scan --tracked
legacy/import_users.py:2:17 [CRITICAL] GitHub Personal Access Token (github-pat) nsi_5e31772c2cb9
  preview: ghp_...PeXq
$ nosecrets ignore nsi_5e31772c2cb9 --path legacy/import_users.py
Added ignore entry to .nosecretsignore
$ cat .nosecretsignore
nsi_5e31772c2cb9:legacy/import_users.py
$ nosecrets scan --tracked
No secrets found
  • scan --tracked
  • ignore
  • .nosecretsignore

Exceptions on purpose, visible in review

site/captures/usecases/exceptions/Detail: Exceptions on purpose, visible in review →
# .nosecrets.toml
[ignore]
paths = ["tests/fixtures/"]

# app.py
DOC_EXAMPLE = "ghp_R2kT9xLm4Qv7Bn3Zc8Wd5Hf1Jg6Ys0AaPeXq"  # @nsi documented example value
STAGING = "ghp_Zp3Lk8Vn2Xc6Qw1Rt5Mb9Hd4Jf7Ys0GaNeUo"

# tests/fixtures/tokens.py
FIXTURE = "ghp_Qw8Er5Ty2Ui7Op1As4Df9Gh3Jk6Lz0XcVbNm"
$ git add -A
$ nosecrets scan --staged
app.py:2:12 [CRITICAL] GitHub Personal Access Token (github-pat) nsi_8c2b7cfaeaba
  preview: ghp_...NeUo
  • @nsi
  • .nosecrets.toml
  • ignore paths

Beyond the built-in rules

A token no rule knows

site/captures/usecases/entropy/Detail: A token no rule knows →
# settings.py
SERVICE_NAME = "billing"
API_KEY = "changeme"
INTERNAL_SERVICE_TOKEN = "q7Zk2vN9xB4mT8wR1cL6hD3fY5jP0sGe"
$ git add settings.py
$ nosecrets scan --staged
settings.py:3:27 [MEDIUM] High Entropy String (high-entropy-string) nsi_294e8f1228c4
  preview: q7Zk...0sGe
  • high-entropy-string
  • medium

Your own token format as a rule

site/captures/usecases/team-rule/Detail: Your own token format as a rule →
# deploy.sh
#!/bin/sh
curl -H "X-Acme-Token: acme_Vn4Kq8Zx2Lm7Tc1Rw5Bh9Dj3" https://deploy.acme.example/run

# team-rules.toml
[[rule]]
id = "acme-service-token"
name = "Acme service token"
severity = "high"
pattern = '''\b(acme_[A-Za-z0-9]{24})\b'''
keywords = ["acme_"]
capture = 1
$ git add deploy.sh
$ nosecrets scan --staged --rules team-rules.toml
deploy.sh:2:24 [HIGH] Acme service token (acme-service-token) nsi_a7570523585c
  preview: acme...
  • --rules
  • custom rule

In scripts and CI

Machine-readable output

site/captures/usecases/json/Detail: Machine-readable output →
# deploy.py
import requests

GITHUB_TOKEN = "ghp_R2kT9xLm4Qv7Bn3Zc8Wd5Hf1Jg6Ys0AaPeXq"

def release(version):
    return requests.post(
        "https://api.github.com/repos/acme/app/releases",
        headers={"Authorization": f"token {GITHUB_TOKEN}"},
        json={"tag_name": version},
    )
$ git add deploy.py
$ nosecrets scan --staged --format json
[
  {
    "path": "deploy.py",
    "line": 3,
    "column": 17,
    "rule_id": "github-pat",
    "rule_name": "GitHub Personal Access Token",
    "severity": "critical",
    "fingerprint": "nsi_5e31772c2cb9",
    "preview": "ghp_...PeXq"
  }
]
  • --format json

A typo in the configuration stops the scan

site/captures/usecases/bad-config/Detail: A typo in the configuration stops the scan →
# .nosecrets.toml
[entropy]
treshold = 4.5

# deploy.py
import requests

GITHUB_TOKEN = "ghp_R2kT9xLm4Qv7Bn3Zc8Wd5Hf1Jg6Ys0AaPeXq"

def release(version):
    return requests.post(
        "https://api.github.com/repos/acme/app/releases",
        headers={"Authorization": f"token {GITHUB_TOKEN}"},
        json={"tag_name": version},
    )
$ git add deploy.py
$ nosecrets scan --staged
Error: failed to parse .nosecrets.toml: TOML parse error at line 2, column 1
  |
2 | treshold = 4.5
  | ^^^^^^^^
unknown field `treshold`, expected one of `enabled`, `min_length`, `threshold`, `require_context`, `allow`

$ echo $?
2
  • exit 2
  • strict configuration