T09 · Insecure Skill Coding Practices
- Location
scripts/expense-tracker.py:9- Finding
Financial records are stored in plaintext without enforced restrictive permissions
- Content
View full analysis
Vulnerability Details
File Location:
scripts/expense-tracker.py, lines 9-10, 23-39, and 136-146
Vulnerability Type: Insecure local storage and insufficient file-permission enforcement
Risk Level: LowVulnerable Code
python DATA_DIR = Path.home() / ".expense-tracker" DATA_FILE = DATA_DIR / "expenses.json"python def init_data(): """Initialize data directory""" DATA_DIR.mkdir(exist_ok=True) if not DATA_FILE.exists(): with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump([], f) def load_expenses(): """Load all expenses""" init_data() with open(DATA_FILE, 'r', encoding='utf-8') as f: return json.load(f) def save_expenses(expenses): """Save expenses""" with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump(expenses, f, ensure_ascii=False, indent=2)python sample_data = { "month": current_month, "total": total, "count": len(month_expenses), "by_category": category_stats } sample_file = Path(__file__).parent.parent / "data" / "sample.json" sample_file.parent.mkdir(exist_ok=True) with open(sample_file, 'w', encoding='utf-8') as f: json.dump(sample_data, f, ensure_ascii=False, indent=2)Technical Analysis
The application stores expense descriptions, amounts, categories, dates, timestamps, and monthly aggregates as unencrypted JSON. Neither the private data directory nor the generated files are assigned explicit restrictive permissions.
Path.mkdir(exist_ok=True)andopen(..., "w")rely on the process-wide umask. On a system with a permissive umask, the resulting directory or files may be readable by other local users. Existing files with unsafe permissions are also reused without correction.The
statscommand creates a second financial-data file atdata/sample.jsoninside the project tree. U ...[truncated 1919 chars]- Remediation
View remediation
Remediation Suggestions
- Create the private data directory with mode
0700and verify or correct its permissions when it already exists. - Create the expense file with mode
0600, rather than relying on the ambient umask. - Write updates atomically to a temporary file in the protected directory, set mode
0600, flush and synchronize the data, and then replace the destination withos.replace. - Refuse to read or overwrite unexpected symbolic links and verify that the resolved data path remains inside the intended protected directory.
- Store generated statistics under
~/.expense-trackerrather than inside the project tree, or avoid persisting the summary when it is needed only for display. - Add generated data paths to
.gitignoreas defense in depth. - Document the data location, plaintext storage behavior, retention policy, and deletion procedure so users can make an informed privacy decision.
- If the threat model includes compromised backups or storage media, support encryption at rest using an operating-system credential store or a well-reviewed encryption library with keys kept separately from the data.
- Create the private data directory with mode
