Introduces a unified security event store and HTTP API that ingests events from any of the configured sources (API audit, Caddy access log, fail2ban, shared_bans, future remote agents) and surfaces them in the dashboard. New files: src/security/event-store.js JSONL-backed store + in-memory query index src/security/host-registry.js Registered hosts with per-host API keys src/security/event-workers.js Tail-followers for Caddy/fail2ban/shared_bans logs routes/security.js Events, hosts, ingest, SSE stream endpoints status/js/security-center.js Dashboard modal with Overview/Events/Hosts tabs SECURITY-FEATURE.md Full feature documentation DEAD-CODE.md, DUP-CODE.md, HARDENING.md Prior audits Modified: src/app.js Mount /api/v1/security/* src/utilities/middleware.js Add ingest endpoints to PUBLIC_ROUTES src/security/audit-logger.js Mirror audit events into security store server.js Start security workers on boot status/build.js Bundle security-center.js status/index.html Add Security button to nav
126 lines
4.1 KiB
Markdown
126 lines
4.1 KiB
Markdown
# DashCaddy Duplicate Code Report
|
|
|
|
> **Generated:** 2026-07-13
|
|
> **Functions scanned:** 107 (≥200 chars body length)
|
|
> **Exact-duplicate groups:** 10
|
|
|
|
## Methodology
|
|
|
|
1. Extract every top-level `function X() { ... }` declaration
|
|
2. Skip functions < 200 chars (helpers, getters, trivial wrappers)
|
|
3. Normalize: strip comments, collapse whitespace, replace all identifiers with placeholder
|
|
4. SHA-1 the normalized body → identical hashes = duplicate bodies
|
|
|
|
## ⚠️ Caveats
|
|
|
|
- **Anonymous functions and arrow functions are not captured** (regex matches `function name(` only)
|
|
- **Class methods are not captured** (would need AST parser)
|
|
- **Near-duplicates with renamed variables are flagged as the same** (that's the point — after normalization, only structure differs)
|
|
- **`module.exports` factory functions are common and look similar** — many route files have a 5-line wrapper like `module.exports = function(ctx) { const router = express.Router(); ... return router; }`. These will show as duplicate groups.
|
|
|
|
## Exact Duplicate Groups
|
|
|
|
Functions whose bodies are byte-identical after normalization (ignoring comments, whitespace, and identifier names).
|
|
|
|
| Hash | Count | Functions |
|
|
|---|---:|---|
|
|
| `5a3372b656b7` | 2 | `base32Encode`, `base32Encode` |
|
|
| `9550727efdf2` | 2 | `base32Decode`, `base32Decode` |
|
|
| `e00959ade524` | 2 | `getSecret`, `getSecret` |
|
|
| `09498fd55b60` | 2 | `initSecret`, `initSecret` |
|
|
| `0f89a717e703` | 2 | `generateCode`, `generateCode` |
|
|
| `d92f81854134` | 2 | `parseCode`, `parseCode` |
|
|
| `8c3ecfea7f7d` | 2 | `parsePayload`, `parsePayload` |
|
|
| `fc844dbaca2f` | 2 | `verifyCode`, `verifyCode` |
|
|
| `221e46c497d9` | 2 | `main`, `main` |
|
|
| `9573dd3cd485` | 2 | `formatBytes`, `formatBytes` |
|
|
|
|
### Top Groups (Detail)
|
|
|
|
#### Hash `5a3372b656b7` (2 copies)
|
|
|
|
- `src/managers/license-keygen.js:33` — `base32Encode()` (374 chars)
|
|
- `license-keygen.js:33` — `base32Encode()` (374 chars)
|
|
|
|
#### Hash `9550727efdf2` (2 copies)
|
|
|
|
- `src/managers/license-keygen.js:48` — `base32Decode()` (415 chars)
|
|
- `license-keygen.js:48` — `base32Decode()` (415 chars)
|
|
|
|
#### Hash `e00959ade524` (2 copies)
|
|
|
|
- `src/managers/license-keygen.js:62` — `getSecret()` (216 chars)
|
|
- `license-keygen.js:62` — `getSecret()` (216 chars)
|
|
|
|
#### Hash `09498fd55b60` (2 copies)
|
|
|
|
- `src/managers/license-keygen.js:70` — `initSecret()` (597 chars)
|
|
- `license-keygen.js:70` — `initSecret()` (597 chars)
|
|
|
|
#### Hash `0f89a717e703` (2 copies)
|
|
|
|
- `src/managers/license-keygen.js:83` — `generateCode()` (1331 chars)
|
|
- `license-keygen.js:83` — `generateCode()` (1331 chars)
|
|
|
|
#### Hash `d92f81854134` (2 copies)
|
|
|
|
- `src/managers/license-keygen.js:118` — `parseCode()` (523 chars)
|
|
- `license-keygen.js:118` — `parseCode()` (523 chars)
|
|
|
|
#### Hash `8c3ecfea7f7d` (2 copies)
|
|
|
|
- `src/managers/license-keygen.js:135` — `parsePayload()` (443 chars)
|
|
- `license-keygen.js:135` — `parsePayload()` (443 chars)
|
|
|
|
#### Hash `fc844dbaca2f` (2 copies)
|
|
|
|
- `src/managers/license-keygen.js:148` — `verifyCode()` (1367 chars)
|
|
- `license-keygen.js:148` — `verifyCode()` (1367 chars)
|
|
|
|
#### Hash `221e46c497d9` (2 copies)
|
|
|
|
- `src/managers/license-keygen.js:188` — `main()` (4428 chars)
|
|
- `license-keygen.js:188` — `main()` (4428 chars)
|
|
|
|
#### Hash `9573dd3cd485` (2 copies)
|
|
|
|
- `routes/backups.js:693` — `formatBytes()` (259 chars)
|
|
- `routes/apps/restore.js:488` — `formatBytes()` (259 chars)
|
|
|
|
## Common Factory Pattern
|
|
|
|
`module.exports = function(ctx) { const router = express.Router(); ... }`
|
|
|
|
**49 files** use this factory wrapper pattern:
|
|
|
|
- `routes/errorlogs.js`
|
|
- `routes/docker-resources.js`
|
|
- `routes/ca.js`
|
|
- `routes/config-drift.js`
|
|
- `routes/containers.js`
|
|
- `routes/context.js`
|
|
- `routes/monitoring.js`
|
|
- `routes/workflows.js`
|
|
- `routes/services.js`
|
|
- `routes/sites.js`
|
|
- `routes/logs.js`
|
|
- `routes/credentials.js`
|
|
- `routes/themes.js`
|
|
- `routes/updates.js`
|
|
- `routes/dns.js`
|
|
- ... and 34 more
|
|
|
|
Could be extracted to a helper:
|
|
|
|
```javascript
|
|
|
|
// src/utilities/route-factory.js
|
|
module.exports = function routeFactory(handlerFn) {
|
|
return function(deps) {
|
|
const router = require('express').Router();
|
|
handlerFn(router, deps);
|
|
return router;
|
|
};
|
|
};
|
|
```
|