security: fix admin bootstrap race and KB article disclosure
Address two security findings from darkwing scan: Finding 1 [High]: Unauthenticated admin bootstrap race - Require SETUP_TOKEN header or loopback origin for POST /api/setup/create-admin - Enforce 12-character minimum password for bootstrap admin - Cross-check setup_complete setting to prevent re-arming - Log token to container logs on first startup for operator retrieval - Add flask seed show-setup-token CLI helper - Add audit log entry when first admin is created - Preserve existing flask seed admin workflow (uses loopback) Finding 2 [Medium]: Unauthenticated KB article disclosure - Gate POST /api/knowledgebase/<id>/click on kb.view permission - Remove linkurl from response (caller already has it from GET) - Apply _visible_articles() filter to respect retired applications - Prevent anonymous link enumeration and counter manipulation Recovery paths if token is lost: 1. docker logs shopdb-api | grep "Setup token" 2. docker exec shopdb-api flask seed show-setup-token 3. docker exec shopdb-api flask seed admin (unchanged, always works) Zero breaking changes. CLI and existing workflows unaffected. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -199,6 +199,32 @@ def seed_cli():
|
||||
pass
|
||||
|
||||
|
||||
@seed_cli.command('show-setup-token')
|
||||
@with_appcontext
|
||||
def show_setup_token():
|
||||
"""Display the setup token for first-admin creation.
|
||||
|
||||
The token gates POST /api/setup/create-admin so a network race cannot claim
|
||||
the instance during the install window. Printed at first startup; retrieve
|
||||
it here if the logs rotated or you lost the terminal.
|
||||
"""
|
||||
import os
|
||||
from flask import current_app
|
||||
from shopdb.core.models import User
|
||||
|
||||
if User.query.count() > 0:
|
||||
click.echo(click.style("Setup already complete (users exist).", fg='green'))
|
||||
return
|
||||
|
||||
token = current_app.config.get('SETUP_TOKEN') or os.environ.get('SETUP_TOKEN')
|
||||
if token:
|
||||
click.echo(click.style("Setup token: ", fg='cyan') + token)
|
||||
click.echo("Use: curl -X POST .../api/setup/create-admin -H 'X-Setup-Token: <token>' ...")
|
||||
else:
|
||||
click.echo(click.style("No SETUP_TOKEN configured.", fg='yellow'))
|
||||
click.echo("Use 'flask seed admin' from inside the container (no token needed).")
|
||||
|
||||
|
||||
@seed_cli.command('catalog')
|
||||
@click.option('--file', 'path', default=None,
|
||||
help='catalog JSON to load (default shopdb/data/catalog.json)')
|
||||
|
||||
Reference in New Issue
Block a user