Repository navigation
[sec-check] Credential denylist misses *_KEY, *_PWD, *_PAT, webhook URLs and DATABASE_URL-style values — repo templates still expand them into prompts and the builder UI #119
Description
Security Finding
Severity: medium
Type: secret-exposure (unsafe-pattern — incomplete denylist)
#46/#47 stopped repo-controlled templates from implicitly expanding credential env vars ({{GITHUB_TOKEN}}) into prompt output and the builder UI. The guard is a name-only denylist, SENSITIVE_ENV_PATTERN in src/autodetect.ts:
/TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|API_?KEY|ACCESS_KEY|PRIVATE_KEY|BEARER|COOKIE|(^|_)AUTH(_|$)/i
It misses several very common credential naming conventions, and README line 304 over-promises what it covers ("anything matching ... KEY ..."), when only API_KEY / ACCESS_KEY / PRIVATE_KEY are matched.
Reproduced at 41c7bbd:
$ env -i PATH="$PATH" HOME=/tmp \
DATABASE_URL='postgres://app:p@db.internal/app' MYSQL_PWD='p' OPENAI_KEY='sk-...' \
SIGNING_KEY='s' SLACK_WEBHOOK_URL='https://hooks.slack.com/services/T/B/x' GITHUB_PAT='ghp_...' \
node dist/cli.js 'db={{DATABASE_URL}} pwd={{MYSQL_PWD}} oa={{OPENAI_KEY}} sig={{SIGNING_KEY}} hook={{SLACK_WEBHOOK_URL}} pat={{GITHUB_PAT}}' --no-interactive
db=postgres://app:p@db.internal/app pwd=p oa=sk-... sig=s hook=https://hooks.slack.com/services/T/B/x pat=ghp_...
Every one of those expanded. The same isSensitiveEnvName() gate is what collectEnvVars() in src/ui.ts uses, so the builder UI's "Show terminal env" list embeds the same values in the served page.
Classes that get through:
*_KEYwithoutAPI_/ACCESS_/PRIVATE_prefix:OPENAI_KEY,STRIPE_KEY,SIGNING_KEY,ENCRYPTION_KEY,MASTER_KEY,HIVE_HEARTBEAT_KEY,LICENSE_KEY*_PWD:MYSQL_PWD,ORACLE_PWD*_PAT:GITHUB_PAT,AZURE_DEVOPS_PAT*WEBHOOK*: Slack/Discord/Teams webhook URLs are bearer secrets*_DSN,*CONNECTION_STRING*,*_URL/*_URIwith userinfo:DATABASE_URL,REDIS_URL,MONGO_URI,SENTRY_DSN— the password is embedded in the value, and no name rule can catch every one of theseJWT*,*HMAC*,*OAUTH*(when not also_TOKEN/_SECRET)
Impact
A cloned repository's checked-in .prompts/*.md template (or a template a user is handed to paste inline) can name any of the above and silently exfiltrate the value into the generated prompt — which is then sent to a hosted AI model or pasted into a chat. The user sees only the finished prompt text and has no indication a variable came from the environment rather than a default. Database passwords (DATABASE_URL) and PATs are the highest-value cases.
Recommendation
Defense in depth, in src/autodetect.ts and reused by src/ui.ts — the implicit env fallback and the UI env listing only; explicit --flag=value stays untouched:
- Widen the name pattern to also match
(^|_)KEYS?(_|$),_PWD(_|$),(^|_)PAT(_|$),WEBHOOK,(^|_)DSN(_|$),CONN(ECTION)?_?STR(ING)?,(^|_)PASS(_|$),PASSPHRASE,JWT,HMAC,OAUTH(keepingPATH,KEYBOARD,PWD,AUTHORnon-matching). - Add a value-shape check
isSensitiveEnvValue(value)so name-agnostic secrets are still refused: URL userinfo with a password (scheme://user:pass@), PEM blocks (-----BEGIN), JWTs (eyJ….eyJ….), and well-known token prefixes (ghp_/gho_/ghu_/ghs_/ghr_/github_pat_,glpat-,sk-/sk_live_/sk_test_,xox[abprs]-,AKIA…,AIza…,npm_…,hvs.). - Fix README line 304 to describe the actual rule.
- Add tests for each class above to
src/autodetect.test.tsandsrc/ui.test.ts.
Longer-term the implicit process.env fallback could be made opt-in (allowlist or --env flag), which removes the class entirely; that is a product decision and is out of scope for this issue.
- name pattern widened
- value-shape check applied to env fallback and UI env listing
- README corrected
- tests
Filed by sec-check agent (ACMM L6 — full mode)
🐝 Hive Agent: security | Instance: hosted-available-oke-11-placeholder-r05x | SHA: 41c7bbd
— hive: agent=sec-check backend=copilot model=claude-fable-5.1 copilot=1.0.88
Activity