SQL_INJECTION_REMEDIATION_REPORT - nself-org/cli GitHub Wiki
Date: 2026-01-31 Auditor: Security Review Severity: CRITICAL Status: IN PROGRESS
A comprehensive security audit has identified approximately 150 SQL injection vulnerabilities across the nself codebase, primarily in billing and multi-tenancy modules. These vulnerabilities pose a CRITICAL security risk as they:
- Allow attackers to access sensitive financial data
- Enable unauthorized modification of billing records
- Permit tenant data access across isolation boundaries
- Could lead to complete database compromise
Immediate action required to remediate these vulnerabilities before production deployment.
| Module | File | Vulnerabilities | Risk Level |
|---|---|---|---|
| Billing Usage | src/lib/billing/usage.sh |
~60 | CRITICAL |
| Billing Reports | src/lib/billing/reports.sh |
~30 | CRITICAL |
| Billing Core | src/lib/billing/core.sh |
~10 | HIGH (partially fixed) |
| Tenant Lifecycle | src/lib/tenant/lifecycle.sh |
~25 | CRITICAL |
| Tenant Core | src/lib/tenant/core.sh |
~20 | HIGH (partially fixed) |
| Tenant Routing | src/lib/tenant/routing.sh |
~15 | HIGH |
| TOTAL | ~150 | CRITICAL |
-
Direct String Interpolation (~100 instances)
# VULNERABLE WHERE customer_id = '${customer_id}' # SAFE WHERE customer_id = :'customer_id'
-
Missing Input Validation (~50 instances)
# VULNERABLE local service="$1" # ... used directly in SQL # SAFE validate_service_name "$service" || return 1
-
Dynamic SQL Construction (~20 instances)
# VULNERABLE where_clause="customer_id = '${customer_id}'" # SAFE - Use parameterized queries only
File: src/lib/billing/usage.sh
Lines: 305-312, 350-357, 389-424, 448-481, 492-514, 525-536, 548-559, 581-593
Vulnerability:
billing_db_query "
SELECT COALESCE(SUM(quantity), 0)
FROM billing_usage_records
WHERE customer_id = '${customer_id}' # INJECTABLE
AND service_name = '${service}' # INJECTABLE
AND recorded_at >= '${start_date}' # INJECTABLE
AND recorded_at <= '${end_date}'; # INJECTABLE
" | tr -d ' 'Impact: Attacker can extract all usage data, billing information, or modify query logic.
Fix:
# Validate all inputs FIRST
validate_customer_id "$customer_id" || return 1
validate_service_name "$service" || return 1
validate_date_format "$start_date" || return 1
validate_date_format "$end_date" || return 1
# Use parameterized query
billing_db_query "
SELECT COALESCE(SUM(quantity), 0)
FROM billing_usage_records
WHERE customer_id = :'customer_id'
AND service_name = :'service_name'
AND recorded_at >= :'start_date'
AND recorded_at <= :'end_date';
" "tuples" "customer_id" "$customer_id" "service_name" "$service" "start_date" "$start_date" "end_date" "$end_date" | tr -d ' 'File: src/lib/billing/usage.sh
Lines: 1202-1228, 1239-1283
Vulnerability:
where_clause="customer_id = '${customer_id}'" # INJECTABLE
if [[ -n "$start_date" ]]; then
where_clause+=" AND recorded_at >= '${start_date}'" # INJECTABLE
fiImpact: Complete data exfiltration via export functionality.
Fix: NEVER build WHERE clauses with string concatenation. Use parameterized queries exclusively.
File: src/lib/billing/reports.sh
Lines: 79-103, 123-135, 143-175
Vulnerability:
billing_db_query "
SELECT month, active_subs, new_subs
FROM monthly_stats
WHERE DATE_TRUNC('month', created_at) >= :'start_month'::date # SAFE
AND DATE_TRUNC('month', created_at) <= :'end_month'::date # SAFE
" "tuples" "start_month" "$start_month" "end_month" "$end_month"Status: ✅ ALREADY FIXED (Good example of proper parameterization)
File: src/lib/tenant/lifecycle.sh
Lines: 34-35, 44-46, 57-61, 70-72, 76-79
Vulnerability:
slug_exists=$(docker exec -i "$(docker_get_container_name postgres)" \
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -t -c \
"SELECT COUNT(*) FROM tenants.tenants WHERE slug = '$tenant_slug'" | tr -d ' \n')
# INJECTABLE ^^^^^^^^^^^^^^^
owner_id=$(docker exec -i "$(docker_get_container_name postgres)" \
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -t -c \
"SELECT id FROM auth.users WHERE email = '$owner_email' LIMIT 1" | tr -d ' \n')
# INJECTABLE ^^^^^^^^^^^
tenant_id=$(docker exec -i "$(docker_get_container_name postgres)" \
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -t -c \
"INSERT INTO tenants.tenants (name, slug, plan_id, owner_user_id)
VALUES ('$tenant_name', '$tenant_slug', '$plan', '$owner_id')
RETURNING id" | tr -d ' \n')
# ALL INJECTABLE ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^ ^^^^^^^^^^Impact: CRITICAL - Allows account takeover, privilege escalation, and tenant isolation bypass.
Fix: Must use safe query wrapper with validation:
# Validate ALL inputs
validate_identifier "$tenant_slug" 100 || return 1
validate_email "$owner_email" || return 1
validate_identifier "$tenant_name" 255 || return 1
validate_identifier "$plan" 50 || return 1
# Use safe query function (defined in src/lib/database/safe-query.sh)
slug_exists=$(safe_query "
SELECT COUNT(*) FROM tenants.tenants WHERE slug = :'param1'
" "$tenant_slug" | tr -d ' \n')
owner_id=$(safe_query "
SELECT id FROM auth.users WHERE email = :'param1' LIMIT 1
" "$owner_email" | tr -d ' \n')
tenant_id=$(safe_query "
INSERT INTO tenants.tenants (name, slug, plan_id, owner_user_id)
VALUES (:'param1', :'param2', :'param3', :'param4')
RETURNING id
" "$tenant_name" "$tenant_slug" "$plan" "$owner_id" | tr -d ' \n')File: src/lib/tenant/lifecycle.sh
Lines: 184-196, 210-214, 271-282, 307-315
Vulnerability:
docker exec -i "$(docker_get_container_name postgres)" \
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c \
"UPDATE tenants.tenants
SET status = 'suspended', suspended_at = NOW(),
metadata = jsonb_set(COALESCE(metadata, '{}'::jsonb), '{suspension_reason}', '\"$reason\"'::jsonb)
WHERE id = '$tenant_id' OR slug = '$tenant_id'" >/dev/null 2>&1
# INJECTABLE ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^Impact: Arbitrary tenant manipulation, data corruption.
File: src/lib/tenant/core.sh
Lines: 434-444, 457-469
Vulnerability:
DELETE FROM tenants.tenant_members tm
USING tenants.tenants t
WHERE tm.tenant_id = t.id
AND (t.id = '$tenant_id' OR t.slug = '$tenant_id') # INJECTABLE
AND tm.user_id = '$user_id'; # INJECTABLEImpact: Unauthorized user removal, privilege manipulation.
Status: pg_query_safe(), others don't.
File: src/lib/billing/reports.sh
Lines: 482-534, 538-568, 572-609
Vulnerability:
service_filter=""
if [[ "$service" != "all" ]]; then
service_filter="AND service_name = '${service}'" # INJECTABLE
fi
billing_db_query "
SELECT date, service_name, total_usage
WHERE recorded_at >= NOW() - INTERVAL '${days} days' # INJECTABLE
${service_filter} # INJECTABLE CLAUSE
" "tuples"Impact: Data exfiltration, query manipulation.
File: src/lib/tenant/routing.sh
Lines: 220-222, 284-289
Vulnerability:
verified=$(docker exec -i "$(docker_get_container_name postgres)" \
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -t -c \
"SELECT is_verified FROM tenants.tenant_domains
WHERE domain = '$domain' AND tenant_id = '$tenant_id'" | tr -d ' \n')
# INJECTABLE ^^^^^^^^ INJECTABLE ^^^^^^^^^^^^^^^Impact: Domain hijacking, certificate fraud.
File: src/lib/billing/usage.sh
Lines: 839-861, 870-892, 901-923
Vulnerability:
where_clause="customer_id = '${customer_id}'" # INJECTABLE
if [[ -n "$start_date" ]]; then
where_clause+=" AND recorded_at >= '${start_date}'" # INJECTABLE
fi
billing_db_query "
SELECT service_name, DATE_TRUNC('hour', recorded_at) as hour
FROM billing_usage_records
WHERE ${where_clause} # ENTIRE CLAUSE INJECTABLE
"Impact: Complete query control, data manipulation.
File: src/lib/billing/reports.sh
Lines: 769-773
Vulnerability:
total_customers=$(billing_db_query "SELECT COUNT(*) FROM billing_customers WHERE deleted_at IS NULL;" "tuples" 2>/dev/null | tr -d ' ')
total_mrr=$(billing_db_query "SELECT COALESCE(SUM(99), 0) FROM billing_subscriptions WHERE status = 'active';" "tuples" 2>/dev/null | tr -d ' ')Status: ✅ SAFE (No user input in these queries)
Target: Week 1 Scope: Fix top 10 critical vulnerabilities
Files to Fix:
-
src/lib/billing/usage.sh- All aggregate functions -
src/lib/billing/usage.sh- All export functions -
src/lib/tenant/lifecycle.sh- Provisioning workflow -
src/lib/tenant/lifecycle.sh- Suspend/delete operations
Approach:
- Add input validation for ALL user inputs
- Convert ALL string interpolation to parameterized queries
- Use
billing_db_query()with proper parameter binding - Use validation functions from
src/lib/utils/validation.sh
Target: Week 2 Scope: Fix remaining ~140 vulnerabilities
Files to Fix:
-
src/lib/billing/reports.sh- All reporting functions -
src/lib/tenant/core.sh- Remaining unsafe queries -
src/lib/tenant/routing.sh- Domain verification queries
Target: Week 3 Scope: Add additional security layers
Tasks:
- Create SQL injection detection tests
- Add rate limiting for query-heavy endpoints
- Implement query auditing/logging
- Add prepared statement enforcement
- Create security regression tests
The codebase already has validation functions in src/lib/utils/validation.sh:
validate_uuid() # Validates UUIDs
validate_email() # Validates email addresses
validate_identifier() # Validates alphanumeric identifiers (table names, slugs)
validate_integer() # Validates integers
validate_boolean() # Validates boolean valuesUsage:
# Validate before use
if ! customer_id=$(validate_uuid "$customer_id" 2>/dev/null); then
error "Invalid customer ID format"
return 1
fi
# Now safe to use in parameterized query
billing_db_query "SELECT * FROM billing_customers WHERE customer_id = :'param1'" "tuples" "param1" "$customer_id"# BEFORE (VULNERABLE)
result=$(billing_db_query "SELECT * FROM table WHERE id = '${id}'" | tr -d ' ')
# AFTER (SAFE)
validate_uuid "$id" || return 1
result=$(billing_db_query "
SELECT * FROM table WHERE id = :'id'
" "tuples" "id" "$id" | tr -d ' ')# BEFORE (VULNERABLE)
billing_db_query "
SELECT * FROM table
WHERE customer_id = '${customer_id}'
AND service = '${service}'
AND date >= '${start_date}'
"
# AFTER (SAFE)
validate_customer_id "$customer_id" || return 1
validate_service_name "$service" || return 1
validate_date_format "$start_date" || return 1
billing_db_query "
SELECT * FROM table
WHERE customer_id = :'customer_id'
AND service = :'service'
AND date >= :'start_date'
" "tuples" \
"customer_id" "$customer_id" \
"service" "$service" \
"start_date" "$start_date"# BEFORE (VULNERABLE)
where_clause="customer_id = '${customer_id}'"
if [[ -n "$service" ]]; then
where_clause+=" AND service = '${service}'"
fi
billing_db_query "SELECT * FROM table WHERE ${where_clause}"
# AFTER (SAFE) - Use COALESCE or conditional logic in SQL
billing_db_query "
SELECT * FROM table
WHERE customer_id = :'customer_id'
AND (:'service' = '' OR service = :'service')
" "tuples" \
"customer_id" "$customer_id" \
"service" "${service:-}"# Create: src/tests/security/test-sql-injection.sh
test_customer_id_injection() {
# Attempt injection
local malicious_id="1' OR '1'='1"
# Should fail validation
if validate_customer_id "$malicious_id" 2>/dev/null; then
fail "Validation should reject SQL injection attempt"
fi
pass "SQL injection blocked by validation"
}
test_parameterized_query_safety() {
# Even with malicious input, parameterized query should treat as literal
local malicious_service="api'; DELETE FROM billing_usage_records; --"
# Should return no results (or error), NOT execute DELETE
local result
result=$(usage_get_service "$malicious_service" "2026-01-01" "2026-01-31" 2>/dev/null)
# Verify no data was deleted
local count
count=$(billing_db_query "SELECT COUNT(*) FROM billing_usage_records" "tuples")
if [[ $count -eq 0 ]]; then
fail "Data was deleted - SQL injection successful!"
fi
pass "Parameterized query prevented SQL injection"
}| Phase | Duration | Vulnerabilities Fixed | Status |
|---|---|---|---|
| Phase 1 | Week 1 | 60 (Critical billing) | 🔴 NOT STARTED |
| Phase 2 | Week 2 | 90 (All remaining) | 🔴 NOT STARTED |
| Phase 3 | Week 3 | N/A (Hardening) | 🔴 NOT STARTED |
| Total | 3 weeks | ~150 | 0% Complete |
- ❌ DO NOT deploy to production until Phase 1 is complete
- ✅ Start Phase 1 remediation immediately
- ✅ Add security tests to CI/CD pipeline
- ✅ Enable SQL query logging for audit trail
- Enforce parameterized queries via linting rules
- Add SQL injection detection to pre-commit hooks
- Implement query whitelisting for high-risk operations
- Add honeypot fields to detect automated attacks
- Enable PostgreSQL audit logging
Before merging any SQL-related changes, verify:
- All user inputs are validated with appropriate validation functions
- All SQL queries use parameterized binding (
:paramsyntax) - No string interpolation (
${var}) in SQL queries - No dynamic WHERE clause construction via string concatenation
- Input validation tests exist for all new functions
- SQL injection tests exist for all query functions
The nself project contains approximately 150 SQL injection vulnerabilities across billing and multi-tenancy modules. These represent a CRITICAL security risk that must be addressed before any production deployment.
The remediation plan outlined above provides a systematic approach to fixing all vulnerabilities within 3 weeks, with the most critical issues addressed in Week 1.
Estimated Effort: 60-80 hours of developer time Risk if not fixed: Complete database compromise, financial data breach, tenant isolation bypass Priority: CRITICAL - Block production deployment until complete
- OWASP SQL Injection: https://owasp.org/www-community/attacks/SQL_Injection
- PostgreSQL Parameterized Queries: https://www.postgresql.org/docs/current/sql-prepare.html
- Input Validation Best Practices: https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
Report Generated: 2026-01-31 Last Updated: 2026-01-31 Next Review: After Phase 1 completion