git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/harness-secrets" ~/.claude/skills/intense-visions-harness-engineering-harness-secrets && rm -rf "$T"
agents/skills/claude-code/harness-secrets/SKILL.mdHarness Secrets
Secret detection, credential hygiene, and vault integration. Find exposed secrets, classify risk, and enforce externalization before they reach production.
When to Use
- When scanning source code for hardcoded secrets, API keys, or credentials
- When auditing environment variable hygiene and
file management.env - On PRs that modify configuration files or add new service integrations
- NOT for general application security review (use harness-security-review)
- NOT for infrastructure credential management (use harness-infrastructure-as-code)
- NOT for CI/CD secret injection (use harness-deployment)
Process
Phase 1: SCAN -- Detect Secrets in Source Code
-
Scan source files for secret patterns. Search for common secret formats:
- API keys: Patterns matching
,sk-
,pk_
,AKIA
,AIza
,ghp_
,glpat-xoxb- - Connection strings: Database URIs with embedded credentials (
)postgres://user:pass@ - Private keys:
,-----BEGIN RSA PRIVATE KEY----------BEGIN EC PRIVATE KEY----- - JWT tokens: Base64-encoded strings matching
header patterneyJ - Generic secrets: Variables named
,password
,secret
,token
with literal string valuesapi_key
- API keys: Patterns matching
-
Scan configuration files. Check files that commonly contain secrets:
,.env
,.env.local
(should be gitignored).env.production
,config/*.json
with credential fieldsconfig/*.yaml
with inline environment valuesdocker-compose.yml
,application.properties
with connection stringsappsettings.json- CI/CD pipeline files with hardcoded values
-
Check
coverage. Verify that sensitive files are excluded from version control:.gitignore
files (except.env*
).env.example
,*.pem
private key files*.key
,credentials/
directoriessecrets/- Service account JSON files (
)*-credentials.json - IDE-specific files that may cache environment variables
-
Scan git history for leaked secrets. Check recent commits:
- Run
for recently added filesgit log --diff-filter=A --name-only - Check if any
or credential files were committed and later removed.env - Flag files that appear in git history but are now gitignored (the secret is still in history)
- Run
-
Present scan results:
Secret Scan: 7 findings in 5 files CRITICAL (2): src/config/database.ts:8 -- Hardcoded PostgreSQL connection string with password src/services/stripe.ts:3 -- Stripe secret key (sk_example_...) HIGH (3): docker-compose.yml:15 -- MySQL root password in plaintext src/config/aws.ts:12 -- AWS access key pattern (AKIA...) .env.production:1 -- File committed to git (should be gitignored) MEDIUM (2): src/utils/auth.ts:45 -- JWT secret as string literal config/app.json:22 -- Generic "apiKey" field with literal value
Phase 2: CLASSIFY -- Categorize by Risk and Type
-
Assign severity levels. Classify each finding:
- CRITICAL: Live production credentials, private keys, cloud provider access keys. Immediate rotation required.
- HIGH: Secrets in committed files, database passwords, service API keys. Rotation strongly recommended.
- MEDIUM: Development-only secrets in source, JWT signing keys, generic tokens. Should be externalized.
- LOW: Example values that look like secrets but are placeholders (
), test-only credentials in test fixtures.YOUR_API_KEY_HERE
-
Identify secret type. Categorize each finding:
- Cloud provider credentials (AWS, GCP, Azure)
- Database credentials (connection strings, passwords)
- Third-party API keys (Stripe, SendGrid, Twilio)
- Authentication secrets (JWT keys, OAuth client secrets)
- Encryption keys (symmetric keys, private keys)
- Internal service tokens (inter-service auth)
-
Assess blast radius. For each CRITICAL and HIGH finding:
- What systems does this credential access?
- Is the credential scoped (read-only, limited permissions) or broad (admin)?
- Is the credential shared across environments?
- When was the credential last rotated?
-
Check for false positives. Verify findings are actual secrets:
- Example/placeholder values in documentation
- Test fixtures with fake credentials
- Base64-encoded non-secret data matching JWT patterns
- Hash values that match key patterns but are not keys
-
Generate classification report:
Classification: CRITICAL: 2 (require immediate rotation) HIGH: 3 (require rotation within 24 hours) MEDIUM: 2 (require externalization) LOW: 0 False positives: 1 (removed from findings) Affected systems: - PostgreSQL database (production) - Stripe payment processing - AWS S3 storage
Phase 3: REMEDIATE -- Extract and Secure Secrets
-
Recommend secret externalization. For each finding, provide the remediation:
- Replace hardcoded value with environment variable reference
- Add the variable to
with a placeholder value.env.example - Add the actual value to the deployment secret store
- Verify
includes the actual.gitignore
file.env
-
Recommend secret management integration. Based on the project's infrastructure:
- HashiCorp Vault: Dynamic secrets, lease-based rotation, transit encryption
- AWS Secrets Manager: Native AWS integration, automatic rotation for RDS
- Google Secret Manager: GCP-native, IAM-based access control
- Azure Key Vault: Azure-native, HSM-backed key storage
- dotenv + CI secrets: Minimum viable approach for smaller projects
-
Recommend rotation procedure. For each CRITICAL and HIGH finding:
- Generate a new credential in the source system
- Update the secret store with the new value
- Deploy the updated configuration
- Verify the service works with the new credential
- Revoke the old credential
- Confirm no systems depend on the old credential
-
Provide code transformation examples. Show before/after for each finding:
// BEFORE (hardcoded) const stripe = new Stripe('sk_example_abc123...'); // AFTER (externalized) const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); -
If
flag is set, apply automatic transformations:--fix- Extract hardcoded values to environment variables
- Add
entries with placeholder values.env.example - Update
if.gitignore
files are not excluded.env - Present the diff for review before committing
Phase 4: VALIDATE -- Verify Remediation Completeness
-
Re-scan after remediation. Run the same scan from Phase 1 to verify:
- All CRITICAL and HIGH findings are resolved
- No new secrets were introduced during remediation
- Environment variable references resolve correctly
-
Verify
coverage. Confirm:.gitignore- All
files (except.env
) are gitignored.env.example - Private key files are gitignored
- The gitignore patterns are specific enough (not overly broad)
- All
-
Verify
completeness. Check that:.env.example- Every environment variable referenced in code has an entry
- Values are placeholders, not actual secrets
- Each entry has a comment describing the variable's purpose
- Required vs. optional variables are clearly marked
-
Check git history for residual exposure. If secrets were previously committed:
- Warn that the secret exists in git history even after removal
- Recommend
or BFG Repo-Cleaner for history rewritinggit filter-repo - Emphasize that rotation is required regardless of history cleanup
- Note that force-push to remote may be required after history rewrite
-
Generate validation report:
Secret Validation: [PASS/WARN/FAIL] Rescan: PASS (0 CRITICAL, 0 HIGH findings) .gitignore: PASS (all sensitive patterns covered) .env.example: WARN (missing STRIPE_WEBHOOK_SECRET entry) Git history: WARN (2 secrets exist in history -- rotation required) Actions remaining: 1. Add STRIPE_WEBHOOK_SECRET to .env.example 2. Rotate PostgreSQL password (exposed in commit abc1234) 3. Rotate Stripe key (exposed in commit def5678) 4. Consider git history rewrite after rotation
Harness Integration
-- Primary invocation for secret scanning and remediation.harness skill run harness-secrets
-- Run after remediation to verify project health.harness validate
-- Complementary mechanical security scan that includes basic secret detection.harness check-security
-- Present findings and gather decisions on remediation approach.emit_interaction
Success Criteria
- All source files are scanned for secret patterns
- Findings are classified by severity with accurate false-positive filtering
- CRITICAL and HIGH findings have specific rotation recommendations
- Environment variable externalization is verified
covers all sensitive file patterns.gitignore
is complete with placeholder values.env.example- Git history exposure is flagged with rotation guidance
Examples
Example: Express.js API with Hardcoded Stripe Keys
Phase 1: SCAN Scanned: 86 files Findings: 4 CRITICAL: src/payments/stripe.ts:5 -- sk_example_EXAMPLE_KEY_REDACTED_0000 HIGH: docker-compose.yml:22 -- POSTGRES_PASSWORD=supersecret MEDIUM: src/config/jwt.ts:3 -- JWT_SECRET = "my-jwt-secret-key" LOW: tests/fixtures/auth.ts:8 -- fake-api-key-for-testing (false positive) Phase 2: CLASSIFY CRITICAL: 1 (Stripe production secret key -- full payment access) HIGH: 1 (PostgreSQL password -- database access) MEDIUM: 1 (JWT secret -- token forgery risk) False positives: 1 (test fixture removed from findings) Phase 3: REMEDIATE 1. Stripe key -> process.env.STRIPE_SECRET_KEY 2. Postgres password -> ${POSTGRES_PASSWORD} in compose, actual value in .env 3. JWT secret -> process.env.JWT_SECRET Added 3 entries to .env.example Updated .gitignore with .env* pattern Phase 4: VALIDATE Rescan: PASS (0 findings) .gitignore: PASS .env.example: PASS (all 3 variables documented) Git history: WARN (Stripe key in commit history) Result: WARN -- secrets externalized, rotation required for Stripe and Postgres
Example: Django Application with AWS Credentials
Phase 1: SCAN Scanned: 124 files Findings: 5 CRITICAL: settings/production.py:45 -- AWS_ACCESS_KEY_ID = "AKIA..." CRITICAL: settings/production.py:46 -- AWS_SECRET_ACCESS_KEY = "wJal..." HIGH: .env.production committed to git (12 secrets inside) MEDIUM: settings/base.py:88 -- SECRET_KEY = "django-insecure-..." MEDIUM: settings/base.py:92 -- DATABASE_URL with embedded password Phase 2: CLASSIFY CRITICAL: 2 (AWS IAM credentials -- full account access) HIGH: 1 (.env.production in git -- 12 leaked values) MEDIUM: 2 (Django secret key and database URL) Phase 3: REMEDIATE 1. AWS credentials -> boto3 credential chain (env vars or IAM role) 2. Remove .env.production from git, add to .gitignore 3. Django SECRET_KEY -> os.environ["DJANGO_SECRET_KEY"] 4. DATABASE_URL -> os.environ["DATABASE_URL"] Recommend: Switch to django-environ for all settings Recommend: Use IAM roles instead of access keys for production Phase 4: VALIDATE Rescan: PASS .gitignore: PASS .env.example: PASS Git history: CRITICAL (AWS keys and .env.production in history) Result: FAIL -- rotation required before deployment, history rewrite recommended
Rationalizations to Reject
| Rationalization | Reality |
|---|---|
| "That key is read-only so it's not a big deal if it leaks" | Read-only credentials still enable data exfiltration, reconnaissance, and discovery of other vulnerabilities. A leaked read-only database credential exposes every row in the database. Scope does not eliminate risk. |
| "We removed it from the file — it's cleaned up now" | Removing a secret from the current tree does not remove it from git history. Anyone with a clone of the repository can recover the secret with . Rotation is required regardless of file deletion. |
| "That's a test environment key, not production" | Test environment credentials are frequently reused, shared informally, and rotated less often. Leaked test keys also reveal credential patterns and naming conventions that help attackers guess production secrets. |
| "It's in a private repo so only our team can see it" | Private repos are accessed by CI/CD systems, third-party integrations, contractors, and former employees. Repository access controls are not a substitute for secret externalization. Breaches routinely originate from compromised internal access. |
| "We'll move it to an environment variable before we deploy" | Intent does not prevent exposure. The secret is in the codebase now and may already be in commit history, CI logs, or developer machine caches. Remediation must happen at the moment of detection, not at deployment time. |
Gates
- No CRITICAL findings may remain unaddressed. Production credentials exposed in source code are blocking. Execution halts until the credential is rotated and the code is remediated.
- No
files with actual secrets committed to git. A committed.env
file containing real credentials is a blocking finding, even if the file is later gitignored..env - No secrets in git history without rotation. If a secret was previously committed, it must be rotated regardless of whether it was removed from the current tree.
- No remediation without verification. The
flag must be followed by a rescan to confirm all findings are resolved.--fix
Escalation
- When a production credential is exposed in a public repository: This is an emergency. Immediately recommend rotating the credential, then address code remediation. Do not wait for a PR review cycle -- rotation must happen within minutes.
- When git history contains secrets and the repo is public: Recommend making the repo private temporarily, rotating all exposed credentials, running BFG Repo-Cleaner, and force-pushing. Note that GitHub caches may retain the data -- contact GitHub support if needed.
- When the team has no secret management infrastructure: Recommend starting with CI/CD platform secrets (GitHub Secrets, GitLab CI variables) as a minimum viable approach. Design a migration path to a dedicated secret manager for later.
- When false positive rate is high: Adjust scan patterns for the project's domain. Add a
file with documented exceptions for known false positives (test fixtures, example values, hash constants)..harness/secret-scan-ignore