tractatus/scripts/check-attack-surface.js
TheFlow b9a301f2a7 feat(security): implement attack surface exposure prevention (inst_084)
Adds comprehensive protection against exposing internal implementation
details in public-facing documentation.

New Governance Rule (inst_084):
- Quadrant: SYSTEM
- Persistence: HIGH
- Scope: Public documents (confidential:false)
- Enforcement: Pre-commit hooks (mandatory)

Implementation:
1. attack-surface-validator.util.js
   - Pattern detection for file paths, API endpoints, admin URLs, ports
   - Frontmatter parsing (respects confidential:true exemption)
   - Code block exemption (doesn't flag technical examples)
   - Intelligent line numbering for violation reporting

2. check-attack-surface.js
   - Pre-commit script that scans staged documents
   - User-friendly violation reporting with suggestions
   - Integration with git workflow

3. Pre-commit hook integration
   - Added as Check #3 in git hooks
   - Runs after prohibited terms, before test requirements
   - Blocks commits with attack surface exposures

Detection Patterns:
 File paths: src/*, public/*, scripts/*
 API endpoints: /api/*, /admin/*
 File naming patterns: *.util.js, *.service.js
 Port numbers in prose
 Connection strings

Exemptions:
- Code blocks (```)
- Inline code (`)
- Confidential documents (confidential:true)
- Internal technical documentation

Security Rationale (Defense-in-Depth):
- Prevents reconnaissance by obscuring architecture
- Reduces attack surface by hiding implementation paths
- Complements credential protection (inst_069/070)
- Part of layered security strategy (inst_072)

Testing:
- Validated against test document with known exposures
- 7 violations detected correctly
- Code block exemption verified
- All expected pattern types detected

Example Violations Blocked:
 "Dashboard at /admin/audit-analytics.html"
 "Administrative Dashboard"
 "GET /api/admin/audit-logs endpoint"
 "Authenticated API for audit data"
 "In activity-classifier.util.js"
 "The activity classifier"

This enforcement prevented the exact security issue discovered in
governance-bi-tools.md which exposed admin paths and API endpoints.

Also fixed prohibited terms checker to exempt instruction-history.json
(which contains prohibited term DEFINITIONS, not violations).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 12:11:43 +13:00

144 lines
4.3 KiB
JavaScript
Executable file

#!/usr/bin/env node
/*
* Copyright 2025 John G Stroh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Pre-commit Attack Surface Check (inst_084)
*
* Scans staged files for attack surface exposures
* Blocks commits that expose internal implementation details in public documents
*/
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const { validateFile } = require('../src/utils/attack-surface-validator.util');
function getStagedFiles() {
try {
const output = execSync('git diff --cached --name-only --diff-filter=ACM', {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'ignore']
});
return output.split('\n').filter(f => f.trim());
} catch (error) {
return [];
}
}
function checkStagedFiles() {
const stagedFiles = getStagedFiles();
if (stagedFiles.length === 0) {
console.log('✅ No attack surface exposure check needed (no staged files)');
return { success: true, exposures: [] };
}
// Filter for documents only
const documentFiles = stagedFiles.filter(f =>
f.endsWith('.md') || f.includes('/docs/')
);
if (documentFiles.length === 0) {
console.log('✅ No documents in staged files');
return { success: true, exposures: [] };
}
console.log(`\n🔍 Scanning ${documentFiles.length} document(s) for attack surface exposure...`);
const allExposures = [];
for (const file of documentFiles) {
const filePath = path.join(process.cwd(), file);
if (!fs.existsSync(filePath)) {
continue; // File was deleted
}
const content = fs.readFileSync(filePath, 'utf8');
const result = validateFile(file, content);
if (!result.allowed) {
allExposures.push({
file,
violations: result.violations
});
}
}
return {
success: allExposures.length === 0,
exposures: allExposures
};
}
function printExposures(exposures) {
console.log('\n' + '━'.repeat(80));
console.log('❌ ATTACK SURFACE EXPOSURE DETECTED (inst_084)');
console.log('━'.repeat(80));
console.log('');
console.log('The following files expose internal implementation details:');
console.log('');
for (const { file, violations } of exposures) {
console.log(`\n🔴 ${file}`);
console.log('');
for (const violation of violations) {
console.log(` Line ${violation.line}: ${violation.description}`);
console.log(` Found: "${violation.match}"`);
console.log(` 💡 ${violation.suggestion}`);
console.log('');
}
}
console.log('━'.repeat(80));
console.log('⚠️ SECURITY RISK: Internal architecture exposed in public documents');
console.log('━'.repeat(80));
console.log('');
console.log('Why this matters:');
console.log(' • Exact file paths → easier reconnaissance for attackers');
console.log(' • API endpoints → attack surface mapping');
console.log(' • Port numbers → network scanning targets');
console.log(' • Internal URLs → direct access attempts');
console.log('');
console.log('Fix: Use generalized component names instead of specific paths');
console.log('');
console.log('Examples:');
console.log(' ❌ "Dashboard at /admin/audit-analytics.html"');
console.log(' ✅ "Administrative Dashboard"');
console.log(' ❌ "GET /api/admin/audit-logs endpoint"');
console.log(' ✅ "Authenticated API for audit data"');
console.log('');
console.log('To bypass (NOT RECOMMENDED):');
console.log(' git commit --no-verify');
console.log('');
}
// Main execution
const result = checkStagedFiles();
if (!result.success) {
printExposures(result.exposures);
process.exit(1);
} else {
if (result.exposures.length === 0) {
console.log('✅ No attack surface exposures detected');
}
process.exit(0);
}