This repository has been archived on 2026-03-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
oubliette/internal/shell/banking/data.go
Torjus Håkestad 8ff029fcb7 feat: add Banking TUI shell using bubbletea
Add an 80s-style green-on-black bank terminal shell ("banking") using
charmbracelet/bubbletea for full-screen TUI rendering over SSH.

Screens: login, main menu, account summary, account detail with
transactions, wire transfer wizard (6-step form capturing routing
number, destination, beneficiary, amount, memo, auth code), transaction
history with pagination, secure messages with breadcrumb content (fake
internal IPs, vault codes), change PIN, and hidden admin access (99)
that locks after 3 failed attempts with COBOL-style error output.

All key actions (login, navigation, wire transfers, admin attempts) are
logged to the session store. Wire transfer data is the honeypot gold.

Configurable via [shell.banking] in TOML: bank_name, terminal_id, region.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 23:17:12 +01:00

259 lines
8.3 KiB
Go

package banking
import (
"fmt"
"time"
)
// Account types.
const (
AcctChecking = "CHECKING"
AcctSavings = "SAVINGS"
AcctMoneyMarket = "MONEY MARKET"
AcctCertDeposit = "CERT OF DEPOSIT"
)
// Account represents a fake bank account.
type Account struct {
Number string
Type string
Balance int64 // cents
}
// Transaction represents a fake bank transaction.
type Transaction struct {
Date string
Description string
Amount int64 // cents (negative for debits)
Balance int64 // running balance in cents
}
// SecureMessage represents a fake internal message.
type SecureMessage struct {
ID int
Date string
From string
Subj string
Body string
Unread bool
}
// WireTransfer captures data entered during the wire transfer wizard.
type WireTransfer struct {
RoutingNumber string
DestAccount string
Beneficiary string
BankName string
Amount string
Memo string
AuthCode string
}
// bankState holds all fake data for a session.
type bankState struct {
Accounts []Account
Transactions map[string][]Transaction // keyed by account number
Messages []SecureMessage
Transfers []WireTransfer
}
func newBankState() *bankState {
now := time.Now()
accounts := []Account{
{Number: "****4821", Type: AcctChecking, Balance: 4738291},
{Number: "****7203", Type: AcctSavings, Balance: 18254100},
{Number: "****9915", Type: AcctMoneyMarket, Balance: 52387450},
{Number: "****1102", Type: AcctCertDeposit, Balance: 25000000},
}
transactions := make(map[string][]Transaction)
transactions["****4821"] = generateCheckingTxns(now, accounts[0].Balance)
transactions["****7203"] = generateSavingsTxns(now, accounts[1].Balance)
transactions["****9915"] = generateMoneyMarketTxns(now, accounts[2].Balance)
transactions["****1102"] = generateCDTxns(now, accounts[3].Balance)
messages := []SecureMessage{
{
ID: 1,
Date: now.Add(-2 * 24 * time.Hour).Format("01/02/2006"),
From: "SYSTEM ADMINISTRATOR",
Subj: "SCHEDULED MAINTENANCE WINDOW",
Unread: true,
Body: fmt.Sprintf(`FROM: SYSTEM ADMINISTRATOR <sysadmin@internal.securebank.local>
DATE: %s
RE: SCHEDULED MAINTENANCE WINDOW
ALL TERMINALS WILL BE OFFLINE FOR MAINTENANCE:
DATE: %s
TIME: 02:00 - 04:00 EST
AFFECTED: ALL REGIONS
DURING THIS WINDOW, THE FOLLOWING SYSTEMS WILL BE UNAVAILABLE:
- WIRE TRANSFER PROCESSING (10.48.2.100:8443)
- ACCOUNT MANAGEMENT (10.48.2.101:8443)
- ACH BATCH PROCESSOR (10.48.2.105:9090)
PLEASE ENSURE ALL PENDING TRANSACTIONS ARE SUBMITTED BEFORE 01:30 EST.
CONTACT: HELPDESK EXT 4400 OR ops-support@internal.securebank.local`,
now.Add(-2*24*time.Hour).Format("01/02/2006 15:04"),
now.Add(5*24*time.Hour).Format("01/02/2006")),
},
{
ID: 2,
Date: now.Add(-5 * 24 * time.Hour).Format("01/02/2006"),
From: "COMPLIANCE DEPT",
Subj: "QUARTERLY AUDIT REMINDER",
Unread: true,
Body: `FROM: COMPLIANCE DEPT <compliance@internal.securebank.local>
RE: QUARTERLY AUDIT REMINDER
ALL BRANCH MANAGERS:
THE Q4 COMPLIANCE AUDIT IS SCHEDULED FOR NEXT WEEK.
PLEASE ENSURE THE FOLLOWING ARE CURRENT:
1. TRANSACTION LOGS EXPORTED TO \\FILESERV01\AUDIT\Q4
2. VAULT ACCESS CODES ROTATED (LAST ROTATION: SEE VAULT-MGMT PORTAL)
3. EMPLOYEE ACCESS REVIEWS COMPLETED IN IAM PORTAL (https://iam.internal:8443)
NOTE: DEFAULT CREDENTIALS FOR THE AUDIT PORTAL HAVE BEEN RESET.
NEW CREDENTIALS DISTRIBUTED VIA SECURE COURIER.
REFERENCE: AUDIT-2024-Q4-0847
VAULT MASTER CODE HINT: FIRST 4 OF ROUTING + BRANCH ZIP (STANDARD FORMAT)`,
},
{
ID: 3,
Date: now.Add(-8 * 24 * time.Hour).Format("01/02/2006"),
From: "IT SECURITY",
Subj: "PASSWORD POLICY UPDATE",
Unread: false,
Body: `FROM: IT SECURITY <itsec@internal.securebank.local>
RE: PASSWORD POLICY UPDATE - EFFECTIVE IMMEDIATELY
ALL STAFF:
PER FEDERAL BANKING REGULATION 12 CFR 748, THE FOLLOWING
PASSWORD POLICY IS NOW IN EFFECT:
- MINIMUM 12 CHARACTERS
- MUST CONTAIN UPPERCASE, LOWERCASE, NUMBER, SPECIAL CHAR
- 90-DAY ROTATION CYCLE
- NO REUSE OF LAST 24 PASSWORDS
LEGACY SYSTEM ACCOUNTS (MAINFRAME, AS/400) ARE EXEMPT UNTIL
MIGRATION IS COMPLETE. CURRENT LEGACY ACCESS:
MAINFRAME: telnet://10.48.1.50:23 (CICS REGION PROD1)
AS/400: tn5250://10.48.1.55 (SUBSYSTEM QINTER)
SERVICE ACCOUNT PASSWORDS ARE MANAGED VIA CYBERARK:
https://pam.internal.securebank.local:8443
TICKET: SEC-2024-1847`,
},
{
ID: 4,
Date: now.Add(-12 * 24 * time.Hour).Format("01/02/2006"),
From: "WIRE OPERATIONS",
Subj: "FEDWIRE CUTOFF TIME CHANGE",
Unread: false,
Body: `FROM: WIRE OPERATIONS <wireops@internal.securebank.local>
RE: FEDWIRE CUTOFF TIME CHANGE
EFFECTIVE NEXT MONDAY, FEDWIRE CUTOFF TIMES ARE:
DOMESTIC WIRES: 16:30 EST (WAS 17:00)
INTERNATIONAL WIRES: 14:00 EST (NO CHANGE)
BOOK TRANSFERS: 17:30 EST (NO CHANGE)
WIRES SUBMITTED AFTER CUTOFF WILL BE QUEUED FOR NEXT
BUSINESS DAY PROCESSING.
FOR EMERGENCY SAME-DAY PROCESSING AFTER CUTOFF:
CONTACT WIRE ROOM: EXT 4450
AUTH CODE REQUIRED (OBTAIN FROM BRANCH MANAGER)
APPROVAL CHAIN: OPS-MGR -> VP-WIRE -> SVP-TREASURY
CORRESPONDENT BANK CONTACTS:
JPMORGAN: wire.ops@jpmc.com / 212-555-0147
CITI: fedwire@citi.com / 212-555-0283`,
},
}
return &bankState{
Accounts: accounts,
Transactions: transactions,
Messages: messages,
}
}
func generateCheckingTxns(now time.Time, endBalance int64) []Transaction {
txns := []Transaction{
{Description: "ACH DEPOSIT - PAYROLL", Amount: 485000},
{Description: "CHECK #1847", Amount: -125000},
{Description: "POS DEBIT - WHOLE FOODS #1284", Amount: -18743},
{Description: "ATM WITHDRAWAL - MAIN ST BRANCH", Amount: -40000},
{Description: "ACH DEBIT - MORTGAGE PMT", Amount: -215000},
{Description: "WIRE TRANSFER IN - REF#8847201", Amount: 1250000},
{Description: "POS DEBIT - SHELL OIL #4492", Amount: -6821},
{Description: "ACH DEPOSIT - PAYROLL", Amount: 485000},
{Description: "CHECK #1848", Amount: -75000},
{Description: "ONLINE TRANSFER TO SAVINGS", Amount: -100000},
{Description: "POS DEBIT - AMAZON.COM", Amount: -14599},
{Description: "ACH DEBIT - ELECTRIC COMPANY", Amount: -18742},
{Description: "ATM WITHDRAWAL - PARK AVE BRANCH", Amount: -20000},
{Description: "WIRE TRANSFER OUT - REF#9014882", Amount: -500000},
{Description: "POS DEBIT - COSTCO #0441", Amount: -28734},
{Description: "ACH DEPOSIT - TAX REFUND", Amount: 342100},
}
return populateTransactions(txns, now, endBalance)
}
func generateSavingsTxns(now time.Time, endBalance int64) []Transaction {
txns := []Transaction{
{Description: "INTEREST PAYMENT", Amount: 4521},
{Description: "ONLINE TRANSFER FROM CHECKING", Amount: 100000},
{Description: "INTEREST PAYMENT", Amount: 4633},
{Description: "ACH DEPOSIT - DIVIDEND PMT", Amount: 125000},
{Description: "ONLINE TRANSFER FROM CHECKING", Amount: 200000},
{Description: "INTEREST PAYMENT", Amount: 4748},
{Description: "WITHDRAWAL - TRANSFER TO MM", Amount: -500000},
{Description: "INTEREST PAYMENT", Amount: 4812},
}
return populateTransactions(txns, now, endBalance)
}
func generateMoneyMarketTxns(now time.Time, endBalance int64) []Transaction {
txns := []Transaction{
{Description: "INTEREST PAYMENT - TIER 3 RATE", Amount: 21847},
{Description: "DEPOSIT - TRANSFER FROM SAVINGS", Amount: 500000},
{Description: "INTEREST PAYMENT - TIER 3 RATE", Amount: 22105},
{Description: "WITHDRAWAL - WIRE TRANSFER", Amount: -1000000},
{Description: "DEPOSIT - ACH TRANSFER", Amount: 750000},
{Description: "INTEREST PAYMENT - TIER 3 RATE", Amount: 22394},
}
return populateTransactions(txns, now, endBalance)
}
func generateCDTxns(now time.Time, endBalance int64) []Transaction {
txns := []Transaction{
{Description: "CERTIFICATE OPENED - 12MO TERM", Amount: 25000000},
{Description: "INTEREST ACCRUAL", Amount: 10417},
{Description: "INTEREST ACCRUAL", Amount: 10417},
{Description: "INTEREST ACCRUAL", Amount: 10417},
}
return populateTransactions(txns, now, endBalance)
}
func populateTransactions(txns []Transaction, now time.Time, endBalance int64) []Transaction {
// Work backwards from end balance to assign dates and running balances.
bal := endBalance
for i := len(txns) - 1; i >= 0; i-- {
txns[i].Balance = bal
txns[i].Date = now.Add(time.Duration(-(len(txns) - i)) * 3 * 24 * time.Hour).Format("01/02/2006")
bal -= txns[i].Amount
}
return txns
}