Publishing Open-Source Packages Safely
How to publish npm packages without accidentally leaking credentials, API keys, or private data. Complete safety system guide.
Publishing Open-Source Packages Safely
The Three-Layer Safety System
ArgoBox has three independent safety checks that prevent publishing credentials, API keys, passwords, or PII to npm:
| Layer | What | When | Can Bypass? |
|---|---|---|---|
| Layer 1: Local Scanner | prepare-publish.mjs script | Before you commit | No (must fix first) |
| Layer 2: Git Hook | Pre-commit block | When you git commit | Yes (with --no-verify) |
| Layer 3: GitHub Actions | CI workflow on GitHub | Before npm publish | No (blocks merge) |
If any layer detects sensitive data, the publish chain stops.
Layer 1: Local Scanner
Run Before Publishing
# Scan one package
node scripts/prepare-publish.mjs @argobox/glass-ui
# Scan all packages at once
node scripts/prepare-publish.mjs --all
# Get detailed JSON output (for automation)
node scripts/prepare-publish.mjs --all --json
What It Detects
The scanner looks for 185+ patterns across 6 categories:
| Category | Examples |
|---|---|
| Credentials | API keys, passwords, GitHub tokens, AWS keys |
| Infrastructure | Private IPs (10.x, 192.168.x), internal domain names |
| Configuration | Database URLs with passwords, internal settings |
| Identifiers | SSNs, account numbers, user IDs |
| Tokens | JWT, session tokens, bearer tokens |
| Private Data | Notes, comments, internal docs |
Example Output
✅ PASS:
✓ @argobox/glass-ui — PASS
└─ 6/6 checks passed
└─ 0 findings
❌ FAIL:
✗ @argobox/glass-ui — BLOCKED
├─ CREDENTIALS: 1 finding
│ └─ src/config.ts:45 — Stripe key detected
│ const STRIPE_KEY = "sk_live_..."
│
└─ INFRASTRUCTURE: 1 finding
└─ .env.example:12 — Private IP detected
DATABASE_URL=mongodb://192.168.1.50:27017
Layer 2: Git Pre-Commit Hook
Automatic on Every Commit
The hook runs automatically when you commit. You don’t need to do anything.
git add your-changes.ts
git commit -m "feat: new feature"
# Hook runs automatically here ⬇️
# ✅ PASS → Commit succeeds
# ❌ FAIL → Commit blocked, must fix first
If Blocked
⚠ Pre-commit hook: release safety check FAILED
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@argobox/glass-ui — BLOCKED
CREDENTIALS (1 finding):
✗ src/config.ts:45 — Pattern "password=..." matched
Fix these files and try again:
1. Edit src/config.ts:45 — remove hardcoded value
2. git add <files>
3. git commit
Override (Not Recommended)
You can skip the hook with --no-verify, but don’t:
git commit -m "fix: something" --no-verify # ⚠️ DANGEROUS
# This bypasses Layer 2, but Layer 3 will still catch it
Layer 3: GitHub Actions CI Workflow
Automatic on Push
Every push to main and every PR automatically runs the release safety scan on GitHub.
No action required — it runs automatically.
View Results
Go to: https://github.com/inovinlabs/argobox/actions
✅ PASS example:
release-safety.yml — ✅ PASSED
├── Package Safety Scan — ✅ All packages passed
├── Metadata Check — ✅ All licenses valid
└── Artifacts: safety-report.json
❌ FAIL example:
release-safety.yml — ❌ FAILED
├── Package Safety Scan — ❌ FAILED
│ └─ @argobox/glass-ui
│ CREDENTIALS: 2 findings
│ └─ src/config.ts:45 — API key pattern matched
│
└── Artifacts: safety-report.json
Branch Protection
If your branch has protection rules, failed checks block merging:
❌ Cannot merge this pull request
Required status checks have not passed
└─ release-safety.yml — FAILED
Fix: Update code, commit, push. CI re-runs automatically.
What Gets Blocked vs. What’s Allowed
❌ These Will Be Blocked
// ❌ Hardcoded API key
const STRIPE_KEY = "sk_live_abc123def456";
// ❌ Password in code
const dbPassword = "MySecurePass123";
// ❌ Private IP address
const SERVER = "mongodb://192.168.1.50:27017";
// ❌ AWS credentials
const awsKey = "AKIAIOSFODNN7EXAMPLE";
// ❌ GitHub token in code
const token = "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";
// ❌ Comments with real credentials
// admin password: MySecret123
✅ These Will Pass
// ✅ Environment variable reference
const apiKey = process.env.STRIPE_KEY;
// ✅ Example/placeholder values (obviously fake)
const exampleKey = "sk_test_placeholder";
// ✅ Public URLs
const API_URL = "https://api.example.com";
// ✅ Documentation (no actual values)
// Set your API key in the .env file
// Format: STRIPE_KEY=sk_live_<alphanumeric>
// ✅ Generic env var names (no values)
process.env.DATABASE_URL
process.env.API_SECRET
// ✅ Sanitized examples
const config = {
apiKey: "YOUR_API_KEY_HERE",
database: "mongodb://localhost:27017",
secret: "change_me_in_production"
};
Publishing Workflow
Step-by-Step Safe Publish
1. Write your code
git checkout -b feature/new-component
# ... edit files ...
2. Test locally (Layer 1)
node scripts/prepare-publish.mjs @argobox/glass-ui
# Expected: ✓ PASS
# If ❌ FAIL, fix the issues
3. Commit (Layer 2 hook runs)
git add .
git commit -m "feat: new component"
# Hook runs automatically
# Expected: ✅ Commit succeeds
4. Push (Layer 3 CI runs)
git push origin feature/new-component
# GitHub Actions runs automatically
# Go to: https://github.com/inovinlabs/argobox/actions
# Expected: ✅ Workflow passes
5. Merge PR (if applicable)
GitHub PR: "Merge when ready"
Expected: ✅ All checks passed, green button
Click: "Merge pull request"
6. Publish to npm (manual or automated)
npm publish
# All 3 safety layers passed ✅
Common Issues & Fixes
Issue: “password = …” Detected
Problem:
const dbPassword = "MySecurePass123";
Fix:
// Move to environment variable
const dbPassword = process.env.DB_PASSWORD;
// Or use async initialization
const dbPassword = await getSecretFromVault();
Issue: “Private IP” Detected
Problem:
const MONGO_URL = "mongodb://192.168.1.50:27017/admin";
Fix:
// Use env var
const MONGO_URL = process.env.MONGO_URL || "mongodb://localhost:27017";
// Or use localhost in code
const MONGO_URL = "mongodb://localhost:27017/admin";
Issue: “API key pattern” Detected
Problem:
const apiKey = "sk_live_abc123def456";
Fix:
// Use environment variable
const apiKey = process.env.STRIPE_KEY;
// Document the format
/**
* Expected format:
* STRIPE_KEY=sk_live_<alphanumeric>
*/
const apiKey = process.env.STRIPE_KEY || throw new Error("STRIPE_KEY required");
Issue: Hook Blocked Commit, Can’t Continue
Don’t use --no-verify.
Instead:
- Read the error message
- Edit the file mentioned
- Replace hardcoded values with env vars
- Run
node scripts/prepare-publish.mjsto verify fix git addthe fixed filegit commitagain
Issue: GitHub Actions Failed After Push
This is actually good! It means the CI caught what slipped through.
Fix:
- Pull latest (to see what was pushed)
- Find the file and line mentioned in the error
- Fix it locally (same as hook issue)
- Commit and push again
- CI re-runs automatically
- Should now show ✅ PASSED
Private Data: Stash & Restore
Protect Internal Files
Some files (internal docs, admin guides, credentials) shouldn’t be published.
1. List them in .private-manifest.json:
{
"files": [
"src/internal/admin.ts",
"docs/internal-api.md"
],
"directories": [
"internal/",
"admin-only/"
]
}
2. Stash before publish:
./scripts/stash-private.sh
# Files are temporarily hidden
3. Publish npm package:
npm publish
# Publishes without the private files
4. Restore afterward:
./scripts/restore-private.sh
# Private files are back
What’s NOT Protected
The safety system catches known patterns, but not everything:
| Not Protected | Why | Solution |
|---|---|---|
| Obfuscated secrets | Patterns don’t match | Manual code review |
| Typos in patterns | System relies on regex | Peer review before commit |
| Newly discovered patterns | System doesn’t know about them yet | Keep patterns updated |
| npm registry history | Once published, it’s public forever | Never make mistakes |
If You Make a Mistake
Immediately unpublish:
npm unpublish @package/name@version
Fix it:
- Remove the credential
- Run
node scripts/prepare-publish.mjsto verify - Commit and push
- Republish with new version number
Notify users:
- Release notes: “Security: removed exposed credential”
- Consider rotating the credential (if real data)
You’re Protected By Default
✅ Layer 1 catches 99% of issues before commit ✅ Layer 2 stops accidental pushes ✅ Layer 3 final gate before npm
Even if all three fail, you’d have to intentionally bypass them.
Your packages are safe. 🔒
Need Help?
- See all patterns: Check
scripts/prepare-publish.mjs - Test the system: See full testing guide in vault (
RELEASE-SAFETY-TESTING.md) - Deep dive: Read
RELEASE-SAFETY-SYSTEM.mdin vault for complete architecture - Questions: Check GitHub Actions logs for exact error messages