Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
- `hardcoded-aws-access-key-id`(AKIA/ASIA), `hardcoded-github-token`(ghp_/github_pat_), `hardcoded-google-api-key`(AIza), `hardcoded-private-key-block`(PEM) — 모두 CRITICAL.
- `supabase-auth-admin-client-usage`(auth.admin.* 클라이언트 노출), `node-open-redirect-user-input`(req 입력 redirect), `insecure-random-security-token`(토큰에 Math.random) — HIGH.
- `wildcard-postmessage-target`(postMessage 대상 '*') — WARNING.
- 제공자별 고정밀 시크릿 탐지 룰 6종 추가(distinctive prefix 기반, 안전 코드 오탐 0 검증):
- `hardcoded-slack-token`(xoxb/xoxa/xoxp/… Slack 토큰), `hardcoded-twilio-credential`(Twilio Account SID `AC…`/API key `SK…`), `hardcoded-sendgrid-api-key`(`SG.` SendGrid 키), `hardcoded-npm-token`(`npm_` npm 토큰), `hardcoded-pypi-token`(`pypi-AgEIcHlwaS…` PyPI 토큰) — 모두 CRITICAL.
- `hardcoded-slack-webhook-url`(`hooks.slack.com/services/…` incoming webhook) — HIGH.
- 모든 룰에 `cwe: [CWE-798]`, `owasp: [A07:2021]` 부여. 기존 룰(OpenAI/Anthropic/Stripe/AWS/GitHub/Google/PEM)과 중복 없음.

### 추가
- `appguardrail scan --sarif <path>` — 정규화된 findings를 SARIF 2.1.0으로 출력합니다. GitHub code scanning(`github/codeql-action/upload-sarif`), VS Code SARIF viewer, Azure DevOps 등 SARIF 소비 도구가 그대로 읽어 Security tab 알림·PR 인라인 주석으로 표시됩니다. severity→level 매핑과 GitHub 랭킹용 `security-severity` 속성, deploy-gate 의미(`deployBlocking`), 재실행 간 안정적인 `partialFingerprints`를 포함합니다.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ plane for history and drift tracking.

Detects:
- Hardcoded secrets (`SUPABASE_SERVICE_ROLE_KEY`, `STRIPE_SECRET_KEY`, etc.)
- Hardcoded provider API tokens by distinctive prefix: OpenAI/Anthropic (`sk-`, `sk-ant-`), AWS (`AKIA`/`ASIA`), GitHub (`ghp_`/`github_pat_`), Google (`AIza`), Slack (`xoxb-…` tokens and `hooks.slack.com` webhooks), Twilio (`AC…`/`SK…`), SendGrid (`SG.…`), npm (`npm_…`), and PyPI (`pypi-AgEIcHlwaS…`)
- Trivy-backed dependency vulnerabilities, secrets, and misconfigurations
- Bandit/Ruff/Semgrep/ZAP findings when their optional external engines are available
- Dangerous Supabase/Firebase usage patterns
Expand Down
72 changes: 72 additions & 0 deletions scanner/rules/secrets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,75 @@ rules:
languages: [generic]
cwe: [CWE-798]
owasp: [A02:2021]

- id: hardcoded-slack-token
patterns:
- pattern-regex: 'xox[baprs]-[0-9A-Za-z-]{10,}'
message: |
Hardcoded Slack token detected (xoxb/xoxa/xoxp/xoxr/xoxs prefix). This
grants access to your Slack workspace and its data. Revoke it in the Slack
app settings, rotate it, and store it as an environment variable.
severity: CRITICAL
languages: [generic]
cwe: [CWE-798]
owasp: [A07:2021]

- id: hardcoded-slack-webhook-url
patterns:
- pattern-regex: 'https://hooks\.slack\.com/services/T[0-9A-Za-z_]+/B[0-9A-Za-z_]+/[0-9A-Za-z]+'
message: |
Hardcoded Slack incoming webhook URL detected. Anyone with this URL can
post messages into your Slack workspace. Delete the webhook in Slack,
recreate it, and load the URL from an environment variable.
severity: HIGH
languages: [generic]
cwe: [CWE-798]
owasp: [A07:2021]

- id: hardcoded-twilio-credential
patterns:
- pattern-regex: '\b(?:AC|SK)[0-9a-fA-F]{32}\b'
message: |
Hardcoded Twilio credential detected (Account SID AC... or API key SK...).
This grants access to your Twilio account and billing. Rotate it in the
Twilio console and store it as an environment variable.
severity: CRITICAL
languages: [generic]
cwe: [CWE-798]
owasp: [A07:2021]

- id: hardcoded-sendgrid-api-key
patterns:
- pattern-regex: 'SG\.[0-9A-Za-z_-]{22}\.[0-9A-Za-z_-]{43}'
message: |
Hardcoded SendGrid API key detected (SG. prefix). This key can send email
on your behalf. Revoke it in the SendGrid dashboard, rotate it, and store
it as an environment variable.
severity: CRITICAL
languages: [generic]
cwe: [CWE-798]
owasp: [A07:2021]

- id: hardcoded-npm-token
patterns:
- pattern-regex: '\bnpm_[0-9A-Za-z]{36}\b'
message: |
Hardcoded npm access token detected (npm_ prefix). This token can publish
packages and read private registries under your account. Revoke it on
npmjs.com and store it as an environment variable (NPM_TOKEN).
severity: CRITICAL
languages: [generic]
cwe: [CWE-798]
owasp: [A07:2021]

- id: hardcoded-pypi-token
patterns:
- pattern-regex: 'pypi-AgEIcHlwaS[0-9A-Za-z_-]{50,}'
message: |
Hardcoded PyPI API token detected (pypi-AgEIcHlwaS prefix). This token can
publish packages under your PyPI account. Revoke it on pypi.org and store
it as an environment variable (TWINE_PASSWORD / PYPI_TOKEN).
severity: CRITICAL
languages: [generic]
cwe: [CWE-798]
owasp: [A07:2021]
75 changes: 75 additions & 0 deletions tests/test_more_secret_rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Precision tests for the additional provider secret detectors."""

import pytest

from scanner.cli.appguardrail import SCAN_RULES

_BY_ID = {}
for _r in SCAN_RULES:
_BY_ID.setdefault(_r["id"], _r)


def _rule(rule_id):
assert rule_id in _BY_ID, f"rule not loaded: {rule_id}"
return _BY_ID[rule_id]


CASES = {
"hardcoded-slack-token": (
[
"xoxb-1234567890-ABCDefgh1234",
'token = "xoxp-0987654321-abcdef-value"',
],
["xoxq-1234567890", "xoxb-short", "box-1234567890abc"],
),
"hardcoded-slack-webhook-url": (
[
"https://hooks.slack.com/services/T00000000/B00000000/"
+ "X" * 24,
"https://hooks.slack.com/services/T1A2B3C4D/B5E6F7G8H/abcDEF123456",
],
[
"https://hooks.slack.com/services/",
"https://example.com/services/T0/B0/xxx",
],
),
"hardcoded-twilio-credential": (
["AC" + "a" * 32, "SK" + "0123456789abcdef0123456789abcdef"],
["ACfoo", "SK" + "g" * 32, "AC" + "a" * 31, "ACCOUNT_NAME_HERE"],
),
"hardcoded-sendgrid-api-key": (
["SG." + "a" * 22 + "." + "b" * 43, "SG.Ab3_-DeFgHiJkLmNoPqRsT." + "x" * 43],
["SG.short.key", "SG." + "a" * 22 + "." + "b" * 10],
),
"hardcoded-npm-token": (
[
"npm_" + "a" * 36,
"//registry.npmjs.org/:_authToken=npm_"
+ "AbCdEf1234567890AbCdEf1234567890AbCd",
],
["npm_short", "npm_" + "a" * 35, "npm install foo"],
),
"hardcoded-pypi-token": (
["pypi-AgEIcHlwaS" + "a" * 60, "pypi-AgEIcHlwaS" + "Ab3_-" * 11],
["pypi-token", "pypi-AgEIcHlwaS" + "a" * 10],
),
}


@pytest.mark.parametrize("rule_id", CASES.keys())
def test_rule_precision(rule_id):
rule = _rule(rule_id)
positives, negatives = CASES[rule_id]
for s in positives:
assert rule["pattern"].search(s), f"{rule_id} should match: {s!r}"
for s in negatives:
assert not rule["pattern"].search(s), f"{rule_id} false-positive on: {s!r}"


def test_severities():
assert _rule("hardcoded-slack-token")["severity"] == "CRITICAL"
assert _rule("hardcoded-slack-webhook-url")["severity"] == "HIGH"
assert _rule("hardcoded-twilio-credential")["severity"] == "CRITICAL"
assert _rule("hardcoded-sendgrid-api-key")["severity"] == "CRITICAL"
assert _rule("hardcoded-npm-token")["severity"] == "CRITICAL"
assert _rule("hardcoded-pypi-token")["severity"] == "CRITICAL"
Loading