fix: adversarial review round — replay-rescue double-charge, discount credit, 2FA/per-IP limits, snapshot encryption, refund reconciliation, VAT, frontend parity, tests+docs
Addresses the adversarial fresh-eyes audit (findings A1-A20) plus review-round fixes: - CRITICAL A1: replay-by-key rescue cross-checks replayed CreatedAt; ccof blind-fail leaves pending with CRITICAL + notification instead of clawing back - A2/A3/A4: till idempotency key restored to unconditional hash; tip rejected in CreateBookingPayment; campaign discount now reduces the charged amount (deposit credit) - A5: admin notifications on blind-fail, manual-refund re-arm, cap-stranded charge-group, webhook FAILED/REJECTED refunds - A6/A10: BuyGiftCard idempotency user-scoped; gift-card slot scan advances past failed rows - A7/A14/A15: 2FA user+IP limiter, SNAPSHOT_ENC_KEY startup validation, accurate pepper/log-delivery docs - A8/A9: snapshot encryption on all write+reuse sites; MPV->SPV effective voucher type (single VAT point) - A11/A12/A13/A16: amount-aware refund reconciliation; completed-booking refund re-validation; till retry dedup; PaymentWasRefunded on SquareClient interface - A17/A18/A19/A20: CI runs npm test; confirm_overflow_tip frontend dialog; unknown-event admin notification; mock token redaction - M7 ConfirmOverflowTip, M9 snapshot encryption, C1 discount ordering regression test - Frontend vitest framework (41 tests), backend coverage for fixed functions, docs corrected (2,269 tests, SUPPORT_EMAIL tokens, resolution status) All 25 backend packages pass; frontend 41/41; build + env-docs green.
This commit is contained in:
+66
-10
@@ -7,21 +7,77 @@ import sys
|
||||
|
||||
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
def find_env_vars_in_code():
|
||||
"""Find all os.Getenv() and import.meta.env.VITE_* calls in the codebase."""
|
||||
env_vars = set()
|
||||
# Matches `const foo = "ENV_NAME"` (with optional explicit string type) on a
|
||||
# single line, plus the same declarations inside a `const ( ... )` block.
|
||||
CONST_SINGLE_RE = re.compile(r'const\s+(\w+)(?:\s+string)?\s*=\s*"([A-Z_][A-Z0-9_]*)"')
|
||||
CONST_BLOCK_RE = re.compile(r'const\s*\(([^)]*)\)')
|
||||
CONST_BLOCK_ITEM_RE = re.compile(r'(\w+)(?:\s+string)?\s*=\s*"([A-Z_][A-Z0-9_]*)"')
|
||||
|
||||
# Search backend Go files for os.Getenv("VAR_NAME")
|
||||
# Matches os.Getenv / os.LookupEnv with either a string literal
|
||||
# (os.Getenv("VAR")) or an identifier (os.Getenv(varName)) that may be a
|
||||
# const holding the env var name (const-indirect reads).
|
||||
GETENV_RE = re.compile(
|
||||
r'os\.(?:Getenv|LookupEnv)\(\s*(?:"([A-Z_][A-Z0-9_]*)"|([A-Za-z_]\w*))\s*\)'
|
||||
)
|
||||
|
||||
|
||||
def go_const_map():
|
||||
"""Return {package_name: {const_name: "ENV_NAME"}} from all backend Go files.
|
||||
|
||||
Consts are package-scoped in Go: a const may be defined in one file and
|
||||
referenced from os.Getenv in another file of the same package (e.g.
|
||||
twoFAPepperEnv defined in twofa.go, read in twofa_prod.go), so collection
|
||||
must be per package rather than per file.
|
||||
"""
|
||||
consts = {} # package -> {name -> value}
|
||||
go_dir = os.path.join(REPO_ROOT, 'backend')
|
||||
for root, dirs, files in os.walk(go_dir):
|
||||
dirs[:] = [d for d in dirs if d not in ('vendor', '.git', 'node_modules')]
|
||||
for f in files:
|
||||
if f.endswith('.go'):
|
||||
path = os.path.join(root, f)
|
||||
with open(path) as fh:
|
||||
for line in fh:
|
||||
m = re.findall(r'os\.Getenv\(["\']([A-Z_][A-Z0-9_]*)["\']', line)
|
||||
env_vars.update(m)
|
||||
if not f.endswith('.go'):
|
||||
continue
|
||||
path = os.path.join(root, f)
|
||||
with open(path) as fh:
|
||||
content = fh.read()
|
||||
pkg_match = re.search(r'^package\s+(\w+)', content, re.MULTILINE)
|
||||
if not pkg_match:
|
||||
continue
|
||||
pkg = pkg_match.group(1)
|
||||
pkg_consts = consts.setdefault(pkg, {})
|
||||
for name, value in CONST_SINGLE_RE.findall(content):
|
||||
pkg_consts[name] = value
|
||||
for block in CONST_BLOCK_RE.findall(content):
|
||||
for name, value in CONST_BLOCK_ITEM_RE.findall(block):
|
||||
pkg_consts[name] = value
|
||||
return consts
|
||||
|
||||
|
||||
def find_env_vars_in_code():
|
||||
"""Find all env vars read in the codebase (os.Getenv/os.LookupEnv in backend
|
||||
Go files, import.meta.env.VITE_* in frontend files)."""
|
||||
env_vars = set()
|
||||
consts = go_const_map()
|
||||
|
||||
# Search backend Go files for os.Getenv / os.LookupEnv reads, including
|
||||
# const-indirect reads (os.Getenv(constName) resolved via the const map).
|
||||
go_dir = os.path.join(REPO_ROOT, 'backend')
|
||||
for root, dirs, files in os.walk(go_dir):
|
||||
dirs[:] = [d for d in dirs if d not in ('vendor', '.git', 'node_modules')]
|
||||
for f in files:
|
||||
if not f.endswith('.go'):
|
||||
continue
|
||||
path = os.path.join(root, f)
|
||||
with open(path) as fh:
|
||||
content = fh.read()
|
||||
pkg_match = re.search(r'^package\s+(\w+)', content, re.MULTILINE)
|
||||
pkg_consts = consts.get(pkg_match.group(1), {}) if pkg_match else {}
|
||||
for literal, identifier in GETENV_RE.findall(content):
|
||||
if literal:
|
||||
env_vars.add(literal)
|
||||
elif identifier in pkg_consts:
|
||||
env_vars.add(pkg_consts[identifier])
|
||||
# Non-const identifiers (e.g. function params like getEnv(key))
|
||||
# cannot be resolved to a specific env var — skip them.
|
||||
|
||||
# Search frontend files for import.meta.env.VITE_* / import.meta.env.*
|
||||
frontend_dir = os.path.join(REPO_ROOT, 'frontend')
|
||||
|
||||
Reference in New Issue
Block a user