Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
1380 commits
Select commit Hold shift + click to select a range
f1e3251
test(purchase): stub late-completion mock so RecoverStrandedApprovals…
cristim May 22, 2026
9246107
test(config): add token_expires_at column to pgxmock GetExecutionByID…
cristim May 22, 2026
1e751af
feat(settings): per-product Coverage and Enabled controls on SP cards…
cristim May 22, 2026
437190c
fix(purchases): record partial success and stamp target account on hi…
cristim May 22, 2026
bb4f454
fix(purchases): align token cancel path with session path and guard i…
cristim May 22, 2026
39355af
fix(gcp/purchases): make CloudSQL/Storage/Memorystore commitments adv…
cristim May 22, 2026
789f0a6
feat(purchases): make Azure reservation purchases idempotent (refs #6…
cristim May 22, 2026
8f0c5da
fix(api/purchases): guard web purchase execution (closes #643, #644, …
cristim May 22, 2026
78d3273
feat(providers/azure/managed-redis): add Azure Managed Redis service …
cristim May 22, 2026
12aba9d
ux(purchases): re-query Savings History chart on filter chip change (…
cristim May 22, 2026
11a190e
feat(purchases): make GCP Compute commitment creation idempotent (#666)
cristim May 22, 2026
2e3baa6
fix(purchases): add execution-tagged logging to executeSinglePurchase…
cristim May 22, 2026
6fa5048
sec(frontend/docs): SRI + version pin on Go-served swagger-ui (closes…
cristim May 22, 2026
cb413e5
test(azure/savingsplans): add regression test for bad payment option …
cristim May 22, 2026
8e2f2e7
fix(execution): recover from panics inside FanOutWithConcurrency goro…
cristim May 22, 2026
441093d
fix(purchases): atomic CAS cancel to close TOCTOU with concurrent app…
cristim May 22, 2026
dc2ee71
feat(providers/azure/synapse): add data-warehouse service client (clo…
cristim May 22, 2026
5441f9e
sec(ci): pin all GitHub Actions to commit SHAs (#536)
cristim May 22, 2026
105338d
test(db): regression test for migration 000027 idempotency (closes #2…
cristim May 22, 2026
0d19c85
fix(purchase): reap purchase_executions stuck in approved/running >10…
cristim May 22, 2026
65ecbf8
fix(azure): switch 7 reservation clients to two-step calculatePrice->…
cristim May 22, 2026
b727ecb
fix(providers/azure): case-insensitive + whitespace-tolerant SKU vali…
cristim May 22, 2026
0aa3058
feat(providers/azure): emit all-upfront + no-upfront payment variants…
cristim May 22, 2026
6c0be15
fix(azure): sanitize Azure DisplayName to [A-Za-z0-9_-] for calculate…
cristim May 22, 2026
b581fe7
fix(purchases): narrow Describe*Offerings + cap pagination (#688) (#690)
cristim May 22, 2026
0635480
feat(aws): rich self-describing reservation names matching Azure #686…
cristim May 22, 2026
423e87c
fix(aws): paginate CE recommendations + RDS engine versions (closes #…
cristim May 22, 2026
f3240f4
fix(ri-exchange): picker UI + offering-id validation + AWS 4xx mappin…
cristim May 25, 2026
e7103d8
fix(ui/recommendations): preserve (All) tri-state and table header af…
cristim May 25, 2026
c04df9d
ci(pre-commit): auth tflint via GITHUB_TOKEN + retry transient flakes…
cristim May 25, 2026
fd33b50
fix(api/history): honor provider/account_ids/start/end query params i…
cristim May 25, 2026
30e8912
fix(purchases): tighten AWS SDK retry budget + bump Lambda timeout to…
cristim May 25, 2026
a44d28e
fix(config): accept provider-canonical payment options in plan valida…
cristim May 25, 2026
464146a
fix(ui/purchases): preserve per-rec payment in Configure Purchase fan…
cristim May 25, 2026
df64d6d
sec(iac): deny deploy-role self-pass and split rds:CreateDBProxy to R…
cristim May 25, 2026
6f6b68b
fix(plans): scope Target Accounts search to selected plan provider (#…
cristim May 25, 2026
fdcede3
chore(ui/history): improve Retry toast wording (#708)
cristim May 25, 2026
7ea945c
fix(plans): live range/digit validation on plan-creation number field…
cristim May 25, 2026
367e447
feat(approvals): surface Account/Term/Payment/Monthly + user email in…
cristim May 25, 2026
5abf40b
fix(plans): honor Account global filter via plan_accounts join (#715)
cristim May 25, 2026
f65998a
sec(iac): Lambda Function URL auth hardening -- CORS wildcard rejecti…
cristim May 26, 2026
1200e08
refactor(iac/aws-lambda): preserve Function URL across count removal …
cristim May 26, 2026
2089692
fix(purchases): AWS-scoped auto-re-drive in RecoverStrandedApprovals …
cristim May 26, 2026
ce05942
fix(azure-arm): grant SP the actual purchase actions via custom role …
cristim May 26, 2026
6fcf6a6
fix(providers/azure): restore IdempotencyToken threading in DoPurchas…
cristim May 26, 2026
4791743
fix(api/history): exclude cancelled executions from KPI dollar totals…
cristim May 27, 2026
67cd495
fix(api/approvals): toast and history use Admin notification_email, n…
cristim May 27, 2026
550a568
fix(approvals): populate Account/Payment/MonthlyCost on Approval queu…
cristim May 27, 2026
67385ec
fix(approvals): use rec-level account fallback in matchesExecution (#…
cristim May 27, 2026
b3c6ac9
fix(ui/history): align partially_completed in Completed chip filter a…
cristim May 27, 2026
cb735f2
fix(purchases): wire topbar filter chips to all three Purchases consu…
cristim May 27, 2026
4c62718
feat(api/auth): flip mutating-route gate to handler-level requirePerm…
cristim May 27, 2026
4add718
fix(scheduler): extend ambient host-account UUID tagging to Azure/GCP…
cristim May 27, 2026
c792272
chore(ci): guard against password handlers that skip base64-decode (#…
cristim May 27, 2026
a555151
feat(commitmentopts/azure): probe Savings Plans offerings for live va…
cristim May 27, 2026
5c15dd3
fix(config): escape LIKE wildcards in registration search filter (#720)
cristim May 27, 2026
9321a59
feat(frontend/recs): render Azure Savings Plans rows + column visibil…
cristim May 27, 2026
8f3e320
feat(ri-exchange): symmetric session-auth approve path (closes #300) …
cristim May 27, 2026
f5b76eb
fix(plans): eliminate universal plans — require target_accounts on cr…
cristim May 27, 2026
f34b036
fix(iac/azure): custom role grants Microsoft.Capacity/reservationOrde…
cristim May 27, 2026
4332f6f
fix(home/kpi): Potential Monthly Savings sums recs same as Opportunit…
cristim May 27, 2026
3931e9e
fix(home/filter): chart honors global Account filter (QA 2.3) (#747)
cristim May 27, 2026
1ba53f6
feat(coverage): per-provider coverage breakdowns (V1) replace placeho…
cristim May 27, 2026
29805ac
feat(ci): assert ARM/TF parity for CUDly Azure custom role (closes #7…
cristim May 27, 2026
3249775
fix(iac/azure): register Microsoft.BillingBenefits RP via custom role…
cristim May 27, 2026
82b5d38
cristim
cristim May 27, 2026
eda0c6b
cristim
cristim May 27, 2026
c5d0ff6
fix(opportunities): align Total Recommendations KPI with Showing-of c…
cristim May 27, 2026
b5d3d00
fix: apply CodeRabbit auto-fixes (#763)
coderabbitai[bot] May 27, 2026
91165e8
fix(home/chart): x-axis spans selected timeframe window (QA 3.1) (#746)
cristim May 27, 2026
6d20076
fix(home/stats): drop /mo suffix from Period Savings total (revert #7…
cristim May 27, 2026
973a42b
docs(dashboard): audit provider savings semantics, enforce 100%-cover…
cristim May 27, 2026
51bdab8
feat(home/chart): add per-service savings-range bar chart (closes #76…
cristim May 27, 2026
6591bd9
feat(settings): Azure and GCP create-override modal (closes #109) (#588)
cristim May 27, 2026
c6b7c61
test(frontend): pin allowed_accounts UI enforcement (#587)
cristim May 27, 2026
0ffdb96
fix(gcp): downgrade compute.regions.list 403 to Warn, return empty re…
cristim May 27, 2026
46b48c5
test(gcp): align mock harness with production client lifecycle (close…
cristim May 27, 2026
dc7cf48
security(iac/gcp): set allow_unauthenticated=false; document ingress …
cristim May 27, 2026
d986b4d
feat(filters): wire provider/account subscriptions to RI Exchange tab…
cristim May 27, 2026
c72601e
feat(home/chart): repoint per-service range bars to potential savings…
cristim May 28, 2026
8c5883d
fix(planned-purchases): Disable plan flips plan toggle to OFF atomically
cristim May 27, 2026
b4c62d6
fix(api/purchases): idempotent disable-plan retry + 404 on missing pl…
cristim May 28, 2026
3f56912
fix(planned-purchases): Edit button uses plan_id, not purchase id (#780)
cristim May 28, 2026
5f3923d
fix(opportunities): prefill Create Purchase Plan modal from selected …
cristim May 28, 2026
dca027c
fix(purchases): add 'paused' to valid_status CHECK constraint so Paus…
cristim May 28, 2026
b441142
fix(opportunities): disable 'Plan from X selected' on heterogeneous s…
cristim May 28, 2026
749bf9a
fix(opportunities): selection toggle no longer reorders table rows (c…
cristim May 28, 2026
99542ff
sec(purchases): remove token from scheduled purchase email query-stri…
cristim May 28, 2026
ba708e3
fix(plans/add-purchases): inline range validation 1-52 (closes #771) …
cristim May 28, 2026
acd9873
test(database): exercise #440 stdout-leak guard + SQL backfill for #3…
cristim May 28, 2026
1a5a7a6
sec: warn on plaintext SCHEDULED_TASK_SECRET when secret-name path is…
cristim May 28, 2026
4529d59
feat(reshape): surface staleness banner when Cost Explorer cache is o…
cristim May 28, 2026
a2a97d7
sec(iac): require non-empty ExternalId on cross-account AssumeRole ca…
cristim May 28, 2026
a31a09e
sec(iac/aws-target): add thumbprint_list validation for custom OIDC i…
cristim May 28, 2026
037508a
feat(commitmentopts): probe Savings Plans offerings live (closes #108…
cristim May 28, 2026
e998421
fix(recommendations): add Savings Plans to GetAllRecommendations fan-…
cristim May 28, 2026
aea9d5a
refactor(frontend): extract parseNumericFilter to shared column-filte…
cristim May 28, 2026
16462db
sec(iac/aws): drop hardcoded branch ref from cleanup-staging.yml (clo…
cristim May 28, 2026
4956d66
sec(email): sanitize AccountName/Provider in SES registration subject…
cristim May 28, 2026
550610b
fix(auth/users): persist email change in admin update path (closes #8…
cristim May 31, 2026
f4d7cdd
fix(azure/gcp/aws): ctx.Err guards + page cap on 22 pagination loops …
cristim May 31, 2026
1464862
feat(inventory+history): align Monthly Cost/Savings columns across bo…
cristim Jun 1, 2026
9458430
fix(home/chart): Potential Savings by Service honors global filter (#…
cristim Jun 1, 2026
1b99460
fix(inventory): Active Commitments + Coverage honor Main Header chips…
cristim Jun 1, 2026
9476456
fix(opportunities): prefill Create Purchase Plan modal from multiple …
cristim Jun 1, 2026
73b3ac9
feat(inventory/ri-exchange): honor Main Header global filter (closes …
cristim Jun 1, 2026
18620bc
feat(ux): URL-addressable sub-tabs (default-first) for Inventory + Ad…
cristim Jun 1, 2026
74148ec
feat(api): public /version endpoint exposes git SHA + build time (#901)
cristim Jun 1, 2026
cec0918
fix(planned-purchases): Pause is visible + reversible (badge, toast, …
cristim Jun 1, 2026
d701f30
fix(profile): apply sign-up's email validator on profile-update + acc…
cristim Jun 1, 2026
5b602d6
fix(admin/policies): Viewer role sees read-only Purchasing Policies (…
cristim Jun 1, 2026
80c20d2
fix(opportunities): hide checkboxes + action bar for viewer role (#876)
cristim Jun 1, 2026
fa1603f
feat(auth/frontend): migrate UI from roles to group-membership author…
cristim Jun 2, 2026
787f95d
fix(server): serve /version on Lambda by excluding it from static-pat…
cristim Jun 2, 2026
8880710
feat(auth): group-membership-only authorization, remove roles, requir…
cristim Jun 2, 2026
1dbeef7
fix(dashboard): merge per-service savings charts and populate current…
cristim Jun 2, 2026
6943207
feat(auth): GET /api/auth/me/permissions + wire frontend canAccess (#…
cristim Jun 3, 2026
40fd854
feat(ui/opportunities): expose AWS recommendation lookback selector (…
cristim Jun 3, 2026
ead285a
fix(frontend): surface Cancel button for cancel-any operators (closes…
cristim Jun 3, 2026
fd57662
fix(frontend/recs): effectiveSavingsPct returns null for AWS rows wit…
cristim Jun 3, 2026
840993c
fix(frontend/inventory+history): align skeleton column counts with #7…
cristim Jun 3, 2026
04607ae
fix(api): getRecommendationDetail 404s on hidden recs (closes #214) (…
cristim Jun 3, 2026
56a7797
feat(recommendations): usage_history + per-row sparkline (closes #239…
cristim Jun 3, 2026
f3920e4
fix(profile): return sentinel errors for wrong password and duplicate…
cristim Jun 3, 2026
eb85a50
feat(api/auth): split execute:ri-exchange from execute:purchases (clo…
cristim Jun 3, 2026
bf81645
ux(recommendations): persist column filters via localStorage (closes …
cristim Jun 3, 2026
e988224
sec(auth): unify login error response for unknown vs wrong-password (…
cristim Jun 3, 2026
e3ecc7c
feat(frontend/riexchange): inline column filters via shared lib (refs…
cristim Jun 3, 2026
681375c
feat(frontend/plans): inline column filters via shared lib (refs #166…
cristim Jun 3, 2026
df82330
ux(recommendations): inline Plan button deep-links into Create Plan m…
cristim Jun 3, 2026
87f739a
ux(frontend): group SP plan types in bulk-buy fan-out modal (closes #…
cristim Jun 3, 2026
0e01aba
ux(settings): show global default in override modal Inherit labels (c…
cristim Jun 3, 2026
b5f6b78
feat(recommendations): group per-plan-type SP rows in table (closes #…
cristim Jun 3, 2026
79f52ec
sec: narrow CSRF exemption for approve/cancel POST endpoints (closes …
cristim Jun 3, 2026
878046e
sec(auth): collapse MFA-not-configured into generic auth failure (clo…
cristim Jun 3, 2026
c00445c
fix(test): use groups not legacy role in recommendations-lookback moc…
cristim Jun 3, 2026
1591ce5
ux(settings): toast-undo for Reset on override rows (closes #113) (#834)
cristim Jun 3, 2026
d086575
fix(api/dashboard): tighten committed_monthly aggregation (status + u…
cristim Jun 3, 2026
623758c
sec(api/auth): return opaque 401 in login catch-all instead of err.Er…
cristim Jun 3, 2026
b6c2733
feat(auth): add Purchaser group + carve execute/approve-any/retry-any…
cristim Jun 3, 2026
53c4900
feat(db): nullable MonthlyCost on PurchaseHistoryRecord (closes #255)…
cristim Jun 3, 2026
6777346
feat(api,recs): execute-any/own permission gate on direct execute (cl…
cristim Jun 3, 2026
5efbf5c
fix(api/purchases): drop removed session.Role shortcut in execute-dir…
cristim Jun 4, 2026
59da2d2
feat(api/dashboard): populate ServiceSavings.CurrentSavings from acti…
cristim Jun 4, 2026
f035285
refactor(auth/mfa): use sentinel errors in mapMFAServiceError (#883)
cristim Jun 4, 2026
2d3f032
fix(auth/seed): relocate Purchaser group to free UUID + recover admin…
cristim Jun 4, 2026
83d5dc8
fix(api/inventory): show account name in Commitments view (closes #95…
cristim Jun 4, 2026
cf521ca
fix(db/migrate): admin-upsert SQL parity with post-057 schema + decou…
cristim Jun 4, 2026
5885697
fix(api): filter purchases by account UUID AND external id (closes #7…
cristim Jun 5, 2026
11981c9
fix(api/plans): differentiate ErrNotFound->404 + add structured slog …
cristim Jun 5, 2026
db5850e
fix(ri-exchange): resolve Azure credentials via per-subscription reso…
cristim Jun 5, 2026
054c91c
sec(purchases): map GetExecutionByID not-found to 404 in getPurchaseD…
cristim Jun 5, 2026
8d575ae
sec(api): cap analytics date range at 366 days to prevent full-table-…
cristim Jun 5, 2026
b6ae07b
fix(recommendations): parseMemoryDBDetails uses rec.ResourceType, err…
cristim Jun 5, 2026
dceb8b0
fix(api/validation): provider-canonical payment-option whitelist (#71…
cristim Jun 5, 2026
0f6e284
feat(cli): configurable --coverage-lookback-days for --target-coverag…
cristim Jun 5, 2026
dd16b94
ops(plans): migration to clean up universal plans (closes #742) (#802)
cristim Jun 5, 2026
aaee6a5
fix(providers/azure): wire Azure Search client into provider (closes …
cristim Jun 5, 2026
3138806
fix(inventory): make per-provider Coverage empty-state filter-aware (…
cristim Jun 5, 2026
69b857a
fix(auth): propagate per-group fetch errors in GetUserPermissions (#920)
cristim Jun 5, 2026
195d56a
fix(frontend/tests): assign Purchaser group to admin mocks (carve-out…
cristim Jun 5, 2026
fb79f4d
fix(db): renumber duplicate migration 000057_cleanup_universal_plans …
cristim Jun 5, 2026
57a6cf9
feat(providers/aws): rich self-describing RI Name tags (closes #687) …
cristim Jun 5, 2026
657e6fd
sec(ci): verify golang-migrate tarball sha256 before install (#537)
cristim Jun 5, 2026
62c8f63
sec(ci): replace secrets:inherit and static Azure creds with OIDC (#538)
cristim Jun 5, 2026
20ce559
fix(api): restore base build - drop removed Session.Role from handler…
cristim Jun 5, 2026
92db4e7
fix(home/kpi): format Current Coverage / Target with one decimal (#967)
cristim Jun 5, 2026
55739c2
perf(frontend/mfa): lazy-load qrcode out of main bundle (closes #511)…
cristim Jun 5, 2026
feb2009
ux(users): surface bulk-actions toolbar (delete + add-to-group) close…
cristim Jun 5, 2026
8ab0d20
fix(accounts): least-privilege accounts list for standard-user filter…
cristim Jun 5, 2026
fa03ee1
feat(purchases): idempotent re-drive for Azure/GCP (closes #639) (#820)
cristim Jun 5, 2026
5f037eb
feat(home/chart): stack Current/Committed band on per-service potenti…
cristim Jun 5, 2026
1220fe5
fix(plans): allow Read-Only users to view plans (closes #999) (#1004)
cristim Jun 5, 2026
cd07f1f
fix(auth): close last-admin TOCTOU with deferred DB trigger (#921)
cristim Jun 5, 2026
735415a
feat(frontend/mobile): hamburger drawer for sidebar nav below 768px (…
cristim Jun 5, 2026
5785a72
ux(settings): bulk-create service override modal applies to multiple …
cristim Jun 6, 2026
85f6da2
feat(frontend/recs): surface VCPU + MemoryGB in table (closes #219) (…
cristim Jun 6, 2026
f3f5616
ux(reshape): composite score ranking for cross-family alternatives (c…
cristim Jun 6, 2026
209db78
feat(aws/recs): populate ComputeDetails.VCPU/MemoryGB via DescribeIns…
cristim Jun 6, 2026
854c58b
perf(azure/compute): wire VCPU/MemoryGB SKU enrichment + ctx-cancel g…
cristim Jun 6, 2026
f7c0e87
feat(home): 2x height + stack Current under Potential on savings-by-s…
cristim Jun 6, 2026
39d63f3
feat(azure/savings-plans): wire SP rec collection into scheduler (#59…
cristim Jun 6, 2026
991eb52
feat(ui): gate Purchases + Inventory pages on view:purchases (closes …
cristim Jun 6, 2026
7485a75
fix(user-mgmt): single toast + stable editor on group update (closes …
cristim Jun 6, 2026
3e53a66
fix(home): round Current Coverage KPI to 1 decimal place (#978) (#980)
cristim Jun 6, 2026
3405060
sec: narrow CSRF exemption for approve/cancel to token-only requests …
cristim Jun 6, 2026
53cc077
feat(user-mgmt): clarify group permissions combine via union (closes …
cristim Jun 6, 2026
cb1abb7
fix(purchases): gate scheduled-purchase management on creator-scope o…
cristim Jun 6, 2026
ede61ad
sec(api): restrict SourceIdentity to admin sessions in GET /api/confi…
cristim Jun 6, 2026
eec7a39
feat(plans): surface legacy no-account plans as Unassigned (closes #9…
cristim Jun 6, 2026
cbdc4be
sec(api): remove APIKeySecretURL/DeploymentAWSAccountID from unauthen…
cristim Jun 6, 2026
43a009c
fix(pkg,gcp): logging level race, MaskToken short-input leak, registr…
cristim Jun 7, 2026
b7012e7
fix(aws/savingsplans): paginate GetExistingCommitments via NextToken …
cristim Jun 7, 2026
0ffce46
fix(aws): payment-option/engine/term correctness gaps in AWS RI servi…
cristim Jun 7, 2026
5fc0899
feat(analytics): finish & wire historical savings snapshots (closes #…
cristim Jun 7, 2026
ee8212e
fix(purchases): close double-buy holes in idempotency, atomic claim, …
cristim Jun 7, 2026
8f20d76
sec(auth): fail-closed MFA, CSRF HMAC derivation, bounded last-used, …
cristim Jun 7, 2026
acb3a17
sec(email): stop broadcasting approval tokens via SNS (closes #1015) …
cristim Jun 7, 2026
a05028a
fix(gcp): wire Count/pricing into GCP converters; refuse fabricated p…
cristim Jun 7, 2026
278a467
fix(exchange): fail loud on over-cap re-quote, empty region, non-posi…
cristim Jun 7, 2026
c85bb9c
sec(api): add "register" rate-limit bucket and fix CSRF prefix match …
cristim Jun 7, 2026
6e470f2
sec(azure): harden HTTP clients + fix fabricated reservation prices i…
cristim Jun 7, 2026
69c5a0c
fix(server): route OIDC in HTTP transport, graceful shutdown, validat…
cristim Jun 7, 2026
e96693c
fix(aws): fail loud on unknown payment-option/term/engine -- remove s…
cristim Jun 7, 2026
f74745e
sec(api): gate GET /api/config and testAccountCredentials on correct …
cristim Jun 7, 2026
45fdc99
fix(services): nil-deref in EmptyBucket, dead Errors field, ecr dedup…
cristim Jun 7, 2026
7bdf5fd
fix(config): replace meaningless time.Now() in DefaultSettings with z…
cristim Jun 7, 2026
5ba8448
refactor(cmd): shared OpenFromEnv DB bootstrap; fix rekey runbook pat…
cristim Jun 7, 2026
ee34869
fix(api): move expireIfStale async; exempt empty-account rows from sc…
cristim Jun 7, 2026
2276bce
fix(iac/azure): move reservation role definition to bootstrap; runtim…
cristim Jun 7, 2026
f5632b3
chore(ci): gofmt base-drifted Go files to unblock pre-commit --all-fi…
cristim Jun 7, 2026
671bd06
chore(terraform): gitignore tfplan.* and *.tfplan.* to block plan bin…
cristim Jun 7, 2026
276404a
fix(api): dedupe per-cell variants in by_service savings to stop ~6x …
cristim Jun 7, 2026
fbe24e2
sec(api): apply allowed_accounts scope to RI exchange listing endpoin…
cristim Jun 8, 2026
ebba640
fix(purchases): atomic ramp-step increment via SELECT FOR UPDATE (#1109)
cristim Jun 8, 2026
edcf9dd
fix(purchases): distinguish not-found from DB errors in GetExecutionB…
cristim Jun 8, 2026
38fa090
fix(frontend/recs): savings is net -- drop double-upfront-subtract fr…
cristim Jun 8, 2026
e31ed68
fix(auth): unify all login failure paths to the generic error message…
cristim Jun 8, 2026
14e953a
fix(azure): derive covered monthly cost instead of hardcoding 0 (#1105)
cristim Jun 8, 2026
763f4ae
feat(recs): plumb provider SavingsPercentage to GUI for CLI/GUI parit…
cristim Jun 8, 2026
f5e706c
fix(recs): disambiguate min-savings dollar (API) vs percent (CLI) fil…
cristim Jun 8, 2026
84fcb9f
fix(frontend): neutralise provider XSS and dedup modal/global listene…
cristim Jun 8, 2026
5a205a6
fix(recommendations): wire MemoryDB NodeType from CE InstanceDetails …
cristim Jun 8, 2026
568ec61
feat(frontend/mobile): viewport meta + touch-target minimums + tap-hi…
cristim Jun 8, 2026
0293ace
sec(api/plans): stop leaking raw account UUID + DB error in plan-acco…
cristim Jun 8, 2026
dddff5a
test(recommendations): assert action-box financial range (#281) (#799)
cristim Jun 8, 2026
3228d98
ux(frontend/recs): enlarge cell-expand chevron (closes #280) (#798)
cristim Jun 8, 2026
45f3c2c
sec(iac): tighten CloudFormation Lambda Function URL CORS wildcards (…
cristim Jun 8, 2026
ad5b348
fix(frontend/test): stub escapeHtmlAttr in utils mocks to unbreak Bui…
cristim Jun 8, 2026
9fada6d
sec(iac): scope kms:GetKeyPolicy away from Resource=* (#427) (#525)
cristim Jun 8, 2026
b31af08
feat(frontend): add amortize-upfront monthly cost toggle (#1112) (#1114)
cristim Jun 8, 2026
608d78a
fix(frontend/test): add missing escapeHtmlAttr stub to utils mock (#1…
cristim Jun 8, 2026
c7448ca
style(frontend/recs): align cost-period selector inline with toolbar …
cristim Jun 8, 2026
7b09546
feat(recommendations): per-rec deterministic seeding in multi-account…
cristim Jun 8, 2026
49e47ad
feat(api-keys): show relative "Last used" per API key (closes #492) (…
cristim Jun 8, 2026
91fb0fe
fix(ci): repair pre-existing base-branch test failures (closes #993) …
cristim Jun 8, 2026
eeaa1a6
test(frontend/auth): cover cross-tab logout sync (closes #493) (#535)
cristim Jun 9, 2026
8e5f139
feat(purchases): in-app revocation within free-cancel window (closes …
cristim Jun 9, 2026
61ecdb5
fix(auth): derive stable CSRF key from encryption key (closes #1120) …
cristim Jun 9, 2026
31d83e7
fix(settings): suppress permission-denied banner on Purchasing polici…
cristim Jun 9, 2026
675390a
fix(frontend/recommendations): stop runaway refresh loop hammering th…
cristim Jun 9, 2026
6ccd291
fix(cli): add recover() guard to queryRDSInstancesInRegions worker go…
cristim Jun 9, 2026
61788c1
fix(dashboard): apply provider filter to savings-history chart (refs …
cristim Jun 9, 2026
e97187a
fix(azure/dashboard): reconcile committed figure with Coverage tab (#…
cristim Jun 9, 2026
9a4d01c
fix(migrations): opt-in dirty auto-heal, raise timeout default, failu…
cristim Jun 9, 2026
0c10f1a
sec: replace math/rand v1 with math/rand/v2 for rate-limiter jitter (…
cristim Jun 9, 2026
05e5561
fix(migrations): repair partially-applied 058-067 schema (idempotent …
cristim Jun 9, 2026
4fce41f
fix(aws/recommendations): case-insensitive EC2 tenancy so AWS RI recs…
cristim Jun 9, 2026
5213ee6
docs(readme): document web interface install + usage (#1128)
cristim Jun 9, 2026
ebfeaed
fix(iac/aws): gate migration-failure alarm behind default-false flag …
cristim Jun 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
49 changes: 49 additions & 0 deletions .air.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Air configuration for hot reload in development
# https://github.com/air-verse/air

root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"

[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./cmd/server"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata", "frontend", "node_modules"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false

[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"

[log]
main_only = false
time = false

[misc]
clean_on_exit = false

[screen]
clear_on_rebuild = false
keep_scroll = true
39 changes: 39 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Terraform state and providers
**/.terraform
*.tfstate
*.tfstate.backup
*.tfbackend

# Frontend build artifacts and dependencies
frontend/node_modules
frontend/coverage
frontend/dist

# IDE and OS
.idea
.vscode
.DS_Store

# Git
.git

# Go vendor (dependencies downloaded at build time)
vendor/

# Temporary files
tmp/

# Docker compose dev files
docker-compose.yml
Dockerfile.dev

# Secrets and credentials
.env
.env.*
*.pem
*.key
*.p12
credentials/
*.tfstate
*.tfstate.backup
.terraform/
120 changes: 120 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# CUDly local development env template
#
# Copy this file to `.env.local` (already in .gitignore) and fill in
# the placeholders. Loaded by: load-env.sh / your IDE / `direnv` —
# CUDly itself reads these via os.Getenv at runtime.
#
# All values here are PLACEHOLDERS. Never commit real secrets to .env*
# files; the .gitignore at the repo root already excludes everything
# matching `.env*` except this template.

# ---------------------------------------------------------------------
# Required: secrets resolver
# ---------------------------------------------------------------------
# SECRET_PROVIDER selects which secret store the resolver fetches from.
# aws | gcp | azure — production: real Secrets Manager / Key Vault
# env — local dev: resolve secret names to env vars
# In `env` mode, every secret-ref var (ADMIN_PASSWORD_SECRET,
# API_KEY_SECRET_ARN, etc.) holds the NAME of another env var whose
# value is the actual secret. See `internal/secrets/env_resolver.go`.
# Pairs with EMAIL_ENABLED=false so the email factory's no-op sender
# kicks in (see PR #333) — otherwise `env` is not a recognised email
# backend and the factory would fail dispatch.
SECRET_PROVIDER=env

# ---------------------------------------------------------------------
# Required: scheduled-task auth mode (no default — must be explicit)
# ---------------------------------------------------------------------
# Selects how the internal /api/scheduled/* endpoints authenticate.
# oidc — production: verify Google-issued OIDC ID tokens
# bearer — shared-secret token in Authorization header
# disabled — local dev: no auth check
# Required by `internal/server/scheduledauth/config.go`; app refuses
# to start when unset.
SCHEDULED_TASK_AUTH_MODE=disabled

# ---------------------------------------------------------------------
# Required: email gate (factory short-circuit, see PR #333)
# ---------------------------------------------------------------------
# When `false`, internal/email/factory.go returns a no-op sender that
# logs each invocation at debug level instead of dispatching to a
# cloud-specific backend. Pair with SECRET_PROVIDER=env for local dev.
EMAIL_ENABLED=false

# ---------------------------------------------------------------------
# Required: credential encryption key
# ---------------------------------------------------------------------
# In production exactly ONE of the per-cloud secret refs is set; the
# Go side reads them in priority order (ARN → NAME → ID → raw KEY).
# For local dev set CREDENTIAL_ENCRYPTION_ALLOW_DEV_KEY=1 to use the
# all-zero dev key without touching a Secrets Manager / Key Vault.
CREDENTIAL_ENCRYPTION_ALLOW_DEV_KEY=1

# CREDENTIAL_ENCRYPTION_KEY_SECRET_ARN=arn:aws:secretsmanager:us-east-1:000000000000:secret:cudly-cred-enc-key-PLACEHOLDER
# CREDENTIAL_ENCRYPTION_KEY_SECRET_NAME=cudly-credential-encryption-key
# CREDENTIAL_ENCRYPTION_KEY_SECRET_ID=cudly-credential-encryption-key
# CREDENTIAL_ENCRYPTION_KEY=<64-hex-chars>

# ---------------------------------------------------------------------
# Required: admin auth + API
# ---------------------------------------------------------------------
# With SECRET_PROVIDER=env, the *_SECRET / *_SECRET_ARN vars hold the
# NAME of another env var whose value is the actual secret. The matched
# env var must then be defined below. Two reasons for the indirection:
# (1) production uses the same var name pointing at a real ARN/name;
# (2) the dev value is co-located with its lookup key so future readers
# can trace the chain in one file.
ADMIN_EMAIL=admin@cudly.local
ADMIN_PASSWORD_SECRET=ADMIN_PASSWORD_DEV
ADMIN_PASSWORD_DEV=LocalDev!Pass123
API_KEY_SECRET_ARN=ADMIN_API_KEY_DEV
ADMIN_API_KEY_DEV=cudly-local-dev-api-key-not-for-prod
# Production examples (override SECRET_PROVIDER and these):
# ADMIN_PASSWORD_SECRET=arn:aws:secretsmanager:us-east-1:000000000000:secret:cudly-admin-password-PLACEHOLDER
# API_KEY_SECRET_ARN=arn:aws:secretsmanager:us-east-1:000000000000:secret:cudly-api-key-PLACEHOLDER

# ---------------------------------------------------------------------
# Optional: web frontend / CORS / dashboard
# ---------------------------------------------------------------------
CORS_ALLOWED_ORIGIN=http://localhost:3000
DASHBOARD_URL=http://localhost:3000
# ENABLE_DASHBOARD=true
# DASHBOARD_BUCKET=cudly-dashboard-PLACEHOLDER

# ---------------------------------------------------------------------
# Optional: PostgreSQL (lazy — unset to skip)
# ---------------------------------------------------------------------
# DB_HOST=localhost
# DB_PORT=5432
# DB_USER=cudly
# DB_NAME=cudly
# DB_PASSWORD_SECRET=arn:aws:secretsmanager:us-east-1:000000000000:secret:cudly-db-PLACEHOLDER
# CUDLY_MIGRATION_TIMEOUT=2m

# ---------------------------------------------------------------------
# Optional: cloud-provider profiles for multi-cloud onboarding
# ---------------------------------------------------------------------
# AWS_CONFIG_FILE=$HOME/.aws/config
# AZURE_TENANT_ID=00000000-0000-0000-0000-000000000000
# AZURE_CLIENT_ID=00000000-0000-0000-0000-000000000000
# AZURE_SUBSCRIPTION_ID=00000000-0000-0000-0000-000000000000
# AZURE_KEY_VAULT_URL=https://cudly-vault-placeholder.vault.azure.net/
# GCP_PROJECT_ID=cudly-placeholder
# AWS_REGION=us-east-1

# ---------------------------------------------------------------------
# Optional: SES / Azure ACS / SendGrid email config
# ---------------------------------------------------------------------
# EMAIL_ADDRESS=noreply@cudly.example
# AZURE_SMTP_HOST=smtp.azurecomm.net
# AZURE_SMTP_USERNAME_SECRET=arn:aws:secretsmanager:us-east-1:000000000000:secret:cudly-smtp-user-PLACEHOLDER
# AZURE_SMTP_PASSWORD_SECRET=arn:aws:secretsmanager:us-east-1:000000000000:secret:cudly-smtp-pass-PLACEHOLDER

# ---------------------------------------------------------------------
# Optional: tunables
# ---------------------------------------------------------------------
# CUDLY_RECOMMENDATION_CACHE_TTL=15m
# CUDLY_MAX_ACCOUNT_PARALLELISM=8
# DEFAULT_PAYMENT_OPTION=no-upfront
# DEFAULT_RAMP_SCHEDULE=quarterly
# ENVIRONMENT=local
45 changes: 45 additions & 0 deletions .gitallowed
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# git-secrets allowlist: regexes that should NOT trigger the AWS-secret scanner.
#
# The scanner's `--register-aws` adds a 12-digit account-ID pattern that
# matches our test fixtures. The values below are obvious test placeholders
# (sequential 123456789012, all-same-digit blocks like 111111111111, and
# countdown patterns like 999888777666) — chosen specifically because they
# cannot be real customer accounts. Adding these is safer than disabling the
# AWS-account check entirely.
#
# DO NOT add a real account ID here. If a real account ID lands in the repo,
# rotate it and treat the leak seriously instead of silencing the scanner.
#
# Format: one regex per line, matched against each line of file content
# (path-scoping is not supported by .gitallowed).

# Sequential test placeholders (ascending and descending).
123456789012
210987654321

# All-same-digit blocks of 12 — used as table-row distinctions in fixtures.
# Listed explicitly rather than via a regex back-reference because not every
# git-secrets build supports back-refs in its allowlist regexes.
000000000000
111111111111
222222222222
333333333333
444444444444
555555555555
666666666666
777777777777
888888888888
999999999999

# Group-block placeholders used in cmd/helpers_test.go variants and
# cmd/multi_service_helpers_test.go.
111222333444
555666777888
999888777666
# Descending-step fixture added in #956 tests (store_postgres_pgxmock_test.go,
# handler_analytics_test.go, handler_history_test.go, handler_dashboard_test.go).
999988887777

# UUID-shaped account ID used in handler_accounts_test.go (synthetic, not a
# real subscription/account).
11111111-1111-1111-1111-111111111111
138 changes: 138 additions & 0 deletions .github/runbooks/compromised-dependency.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Runbook: Compromised Dependency

**Trigger**: A Go module, npm package, Docker base image, or GitHub Action used by CUDly has been found to be malicious or critically vulnerable.

**Owner**: On-call engineer
**Severity**: P1 (active exploitation) / P2 (known critical CVE, no evidence of exploitation)

---

## Step 1: Assess Impact

```bash
# For Go dependency CVE
govulncheck ./...

# For npm dependency CVE
cd frontend && npm audit

# For Docker image CVE
trivy image <IMAGE_URI>

# For GitHub Actions compromise
# Check: https://github.com/advisories (filter by Actions category)
```

Questions to answer:

- Is the vulnerable code path reachable in production?
- Does exploitation require authentication?
- Is there evidence of active exploitation in logs?

---

## Step 2: Pin to Safe Version Immediately

### Go Dependency

```bash
# Pin to last known-good version
go get github.com/affected/package@v1.2.3-safe

# If no safe version exists, pin to a known commit hash
go get github.com/affected/package@<SAFE_COMMIT_SHA>

go mod tidy
go mod verify

# Run tests
go test ./...
```

### npm Dependency

```bash
cd frontend

# Force a specific version
npm install affected-package@<SAFE_VERSION>

# Or use npm audit fix
npm audit fix

# Verify
npm audit
npm test
```

### Docker Base Image

Update the `FROM` line in Dockerfile(s) to a patched version:

```dockerfile
# Before
FROM golang:1.25.4-alpine3.21

# After (example: patch to new minor that fixes CVE)
FROM golang:1.25.5-alpine3.21
```

Rebuild and push the container image.

### GitHub Action

Pin the compromised action to the last known-good commit SHA:

```yaml
# Before (vulnerable)
- uses: some-org/some-action@v1.2.3

# After (pinned to safe commit)
- uses: some-org/some-action@<SAFE_COMMIT_SHA>
```

Or remove the action entirely and replace with equivalent logic using trusted actions or shell commands.

---

## Step 3: Deploy the Fix

```bash
# Tag and push
git add go.mod go.sum Dockerfile
git commit -m "security: pin <package> to safe version (CVE-YYYY-XXXXX)"
git push

# CI/CD will build and deploy
# If CI is also affected by the compromised dependency, run manually:
make build && make push
```

---

## Step 4: Investigate for Active Exploitation

```bash
# Check application logs for exploitation patterns
aws logs filter-log-events \
--log-group-name /aws/lambda/cudly \
--filter-pattern "<exploitation_signature>" \
--start-time <CVE_DISCLOSURE_EPOCH_MS>

# Check CloudTrail for unexpected API calls from application role
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=Username,AttributeValue=<APP_ROLE_NAME> \
--start-time <CVE_DISCLOSURE_TIME>
```

---

## Step 5: Verify & Post-Mortem

- [ ] Confirm `govulncheck` / `npm audit` / `trivy` no longer report the CVE
- [ ] Run full test suite
- [ ] Deploy to staging, then production
- [ ] Document the incident timeline
- [ ] Add the CVE to monitoring rules (detect if patched version is reverted)
- [ ] Review dependency update cadence — consider automating with Dependabot/Renovate
- [ ] Review GitHub Actions pinning strategy → see SC-003 recommendation
Loading
Loading