Files
shopdb-flask/.claude/archived/DARKWING-FINDINGS.md
Proudlock bd48814021 Reorganize repository structure and clean up documentation
Clean up and organize the flask-shopdb repository for better
maintainability and discoverability:

## Changes

- Update .gitignore for build artifacts (publish/, data/, 122M total)
- Reorganize .claude/ into 6 subdirectories with README navigation
- Consolidate overlapping MCP setup guides into MCP-QUICK-START.md
- Move deployment docs from root to docs/deployment/
- Archive dated status reports to .claude/archived/
- Delete temporary files (logs, backups, empty reports, ~50K)

## New Structure

.claude/ organized into:
- setup/ - MCP, VM, Hyper-V, GitHub setup guides + scripts (17 files)
- plans/ - Feature plans and protocols (5 files)
- maintenance/ - Deployment checklists and reports (4 files)
- archived/ - Historical documentation (9 files)
- skills/ - Claude Code automation (5 files)
- workflows/ - Security audit workflow (1 file)

docs/deployment/ created with 6 deployment guides:
- LOCAL-DEV-SETUP.md, DOCKER-SETUP.md, DOCKER-PREBUILT-APPROACH.md
- PRODUCTION-READINESS.md, DEPLOY-TO-VM.md, README.md

Root directory cleaned to essential docs only:
- CLAUDE.md, README.md, CONTRIBUTING.md, CHANGELOG.md

Benefits:
- Clear navigation via README.md in each subdirectory
- No duplicate/overlapping documentation
- Large artifacts properly gitignored (won't bloat repository)
- Historical context preserved in archived/

See .claude/CLEANUP-SUMMARY.md for complete details.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-09-11 15:45:05 -04:00

6.3 KiB

Darkwing Security Findings - 2026-09-07

Scan Run: 34093911764 (wj branch) Findings: 2 (1 High, 1 Medium) Status: Patches ready to apply


Finding 1: [High] Unauthenticated Admin Bootstrap Race

ID: setup-create-admin-unauthenticated File: shopdb/core/api/setup.py:39

Problem

POST /api/setup/create-admin has no authentication. Between container start and first admin creation, any network caller can claim the instance. Every documented deploy path has this window:

docker-compose up -d    # API listening
flask db upgrade        # Migrations run
# <-- RACE WINDOW: anyone can POST /api/setup/create-admin
flask seed admin        # Operator creates admin

Impact

  • Total control: admin role bypasses all permission checks
  • No password strength requirement (1-char accepted)
  • No audit trail (creation is silent)
  • Window reopens on restore to empty schema

Fix Applied

Requires proof of operator control:

Option A: Setup token (remote install)

export SETUP_TOKEN=$(openssl rand -hex 32)
curl -X POST http://host/api/setup/create-admin \
  -H "X-Setup-Token: $SETUP_TOKEN" \
  -d '{"username":"admin","email":"a@b.c","password":"SecurePass123"}'

Option B: Loopback (local install)

# From inside container or via SSH to host
curl -X POST http://localhost:5000/api/setup/create-admin \
  -d '{"username":"admin","email":"a@b.c","password":"SecurePass123"}'

Option C: CLI (unchanged, always works)

docker exec -it shopdb-api flask seed admin

Additional Hardening

  • 12-character password minimum enforced
  • Cross-checks setup_complete setting
  • Logs "First administrator 'X' created from Y.Y.Y.Y"
  • Token printed to container logs on first startup
  • New CLI helper: flask seed show-setup-token

Recovery Paths

Lost the token? Three options:

  1. docker logs shopdb-api | grep "Setup token"
  2. docker exec shopdb-api flask seed show-setup-token
  3. docker exec -it shopdb-api flask seed admin (just works)

Finding 2: [Medium] Unauthenticated KB Article Disclosure

ID: kb-click-unauth-linkurl-disclosure File: plugins/knowledgebase/api/routes.py:182

Problem

POST /api/knowledgebase/<id>/click has @jwt_required(optional=True) instead of @require_permission('kb.view'). Anonymous callers can:

  • Enumerate article IDs 1..N and harvest link URLs
  • Bypass visibility filter (retired topics exposed)
  • Inflate click counters unbounded (DB write spam)
  • URLs may contain tokens/keys in query strings

Compare with sibling route:

@knowledgebase_bp.route('/<int:link_id>', methods=['GET'])
@require_permission('kb.view')  # <-- gated
def get_article(link_id: int):

@knowledgebase_bp.route('/<int:link_id>/click', methods=['POST'])
@jwt_required(optional=True)  # <-- NOT gated
def track_click(link_id: int):
    return success_response({'linkurl': article.linkurl, ...})  # <-- leaks URL

Impact

  • Leaks internal documentation URLs (SharePoint, Confluence, etc.)
  • URLs may contain auth tokens in query strings
  • Unauthenticated write primitive (DB transactions)
  • Explicitly exempted from authz sweep in tests

Fix Applied

  • Gate on kb.view like other KB reads
  • Remove linkurl from response (caller already has it)
  • Apply same _visible_articles() filter as list/detail
  • Counter-only response: {'clicks': N}

Applying the Patch

cd /c/Users/270015376/Documents/github/flask-shopdb
git apply darkwing-fixes.patch

Or review and apply manually.


Testing

Test 1: Admin bootstrap now requires token

# Without token - should fail
curl -X POST http://localhost:5000/api/setup/create-admin \
  -H "Content-Type: application/json" \
  -d '{"username":"attacker","email":"a@b.c","password":"SecurePass123"}'
# Expected: 403 "requires the setup token"

# With token - should succeed
curl -X POST http://localhost:5000/api/setup/create-admin \
  -H "Content-Type: application/json" \
  -H "X-Setup-Token: $SETUP_TOKEN" \
  -d '{"username":"admin","email":"admin@example.com","password":"SecurePass123"}'
# Expected: 201 Created

Test 2: Password minimum enforced

curl -X POST http://localhost:5000/api/setup/create-admin \
  -H "X-Setup-Token: $SETUP_TOKEN" \
  -d '{"username":"admin","email":"a@b.c","password":"short"}'
# Expected: 400 "password must be at least 12 characters"

Test 3: KB click gated

# Without auth - should fail
curl -X POST http://localhost:5000/api/knowledgebase/1/click
# Expected: 401 Unauthorized

# With auth - should succeed (no linkurl in response)
curl -X POST http://localhost:5000/api/knowledgebase/1/click \
  -H "Authorization: Bearer $TOKEN"
# Expected: {"status":"success","data":{"clicks":N}}

Deploy Workflow Changes

Current (Unsafe)

docker-compose up -d
docker exec shopdb-api flask db upgrade
# <-- race window
docker exec shopdb-api flask seed admin

After Patch (Safe)

# Generate token first
$token = openssl rand -hex 32
echo "SETUP_TOKEN=$token" >> .env

docker-compose up -d
# Token logged to console: "Setup token: abc123..."

docker exec shopdb-api flask db upgrade
docker exec shopdb-api flask seed admin
# OR: curl with X-Setup-Token header

Risk Assessment

Before Patch

  • Admin Bootstrap: HIGH - Network race, no password requirements, silent
  • KB Disclosure: MEDIUM - Leaks URLs, unauthenticated writes, visibility bypass

After Patch

  • Admin Bootstrap: LOW - Requires token or localhost, 12-char password, audited
  • KB Disclosure: LOW - Gated on permission like other reads

Files Changed

  1. shopdb/__init__.py - Token logging on first startup
  2. shopdb/cli/__init__.py - New flask seed show-setup-token command
  3. shopdb/core/api/setup.py - Token/loopback auth, password minimum, audit log
  4. plugins/knowledgebase/api/routes.py - Gate click route, remove linkurl

Rollout Priority

Recommendation: Apply in next maintenance window

Urgency:

  • WJ (prod): Medium - Air-gapped, limited exposure, but closes audit finding
  • OPS (dev): Low - Internal network, can wait for next deploy
  • WILM (mirror): Low - Read-only mirror, no setup route active

Compatibility: Zero breaking changes

  • CLI flask seed admin unchanged
  • Existing authenticated workflows unaffected
  • Web wizard needs token in URL param (one-line frontend change)