Skip to content

Security Policy

Overview

Israeli Bank Importer handles sensitive financial credentials and data. This document outlines security best practices and our security policies.


🔐 Credential Security

Configuration File Protection

CRITICAL: Your config.json contains bank credentials and must be protected.

# Set restrictive permissions (owner read/write only)
chmod 600 config.json

# Verify permissions
ls -la config.json
# Should show: -rw------- (600)

For stronger protection, split and encrypt your config:

  1. Split config: separate credentials.json (secrets) from config.json (settings)
  2. Encrypt credentials: CREDENTIALS_ENCRYPTION_PASSWORD=pass node scripts/encrypt-config.js credentials.json

  3. AES-256-GCM encryption (OWASP recommended)

  4. PBKDF2 key derivation (100,000 iterations)
  5. Decrypted only in memory — never written to disk
  6. Supports CREDENTIALS_ENCRYPTION_PASSWORD env var (and old CONFIG_PASSWORD)
  7. Even if someone steals the file, they can't read your credentials

Never Commit Credentials

  • ✅ config.json is in .gitignore by default
  • ❌ NEVER commit config.json to version control
  • ❌ NEVER share config.json publicly
  • ❌ NEVER paste config contents in issues or forums

Environment Variables (Alternative)

For CI/CD or automated deployments, consider using environment variables instead of config.json:

# Example for Bank Discount
ACTUAL_SERVER_URL=https://your-server.com
ACTUAL_PASSWORD=your_password
DISCOUNT_ID=your_id
DISCOUNT_PASSWORD=your_password
DISCOUNT_NUM=your_num

🐳 Docker Security

Required Capabilities

The importer requires SYS_ADMIN capability for Camoufox (Firefox) browser sandboxing:

cap_add:
  - SYS_ADMIN

Why is this needed?

  • Camoufox (Firefox) requires kernel namespaces for browser sandboxing
  • This is standard for headless browser automation
  • Without it, the browser cannot launch

Is this safe?

  • ✅ Safe when running trusted code (this open-source project)
  • ✅ Container still runs as non-root user (node)
  • ✅ No other privileged operations are performed
  • ⚠️ Only run trusted Docker images with this capability

Volume Mounts

Mount config.json as read-only:

volumes:
  - ./config.json:/app/config.json:ro  # Note the :ro flag

This prevents the container from modifying your configuration file.

Container User

The Dockerfile runs as non-root user node (UID 1000):

USER node

This follows the principle of least privilege.

Directory Permissions

As of v1.0, directories use secure permissions:

# Before: drwxrwxrwx (777) - INSECURE
# After:  drwxr-xr-x (755) - SECURE

# Owner (node): read, write, execute
# Group: read, execute
# Others: read, execute

🔒 Data Storage

Persistent Volumes

The importer uses these volumes:

Volume Purpose Sensitivity
config.json Bank credentials 🔴 HIGH
data/ Actual Budget cache, long-term bank tokens, import history, OTP requests, push device tokens 🔴 HIGH
cache/ Scraper cache 🟡 MEDIUM
chrome-data/ Browser profile (legacy, unused with Camoufox) 🟢 LOW

Recommendations:

  • Store volumes on encrypted filesystem
  • Limit access to authorized users only
  • Regular backups of config.json (stored securely)
  • Delete old cache periodically

Data at Rest

  • Config file contains plain-text credentials
  • The portal saves config.json and credentials.json owner-only (0600). Each is written under a random name, created exclusively, then renamed into place, so a link planted beside it is never followed. A __proto__ key in either file is ignored when it is read
  • The app sign-ins (app-tokens.json, on the data volume) are owner-only (0600) and hold a SHA-256 hash of each app refresh token, never the token itself. A file an earlier release left readable to others is made owner-only the next time it is read. The portal never writes over a token file it cannot read: an app sign-in or refresh then fails with a server error instead of signing every phone out
  • A portal server error never carries its cause: it answers a fixed message such as Internal server error or Failed to persist configuration, and the cause (a file path, an error code) goes to the portal log
  • bank-tokens.json on the data volume is an owner-only (0600) file of long-term tokens that skip the SMS login. With CREDENTIALS_ENCRYPTION_PASSWORD (or the legacy CONFIG_PASSWORD) set, each token the importer saves is sealed under it; without either, each token it saves is plain text. Delete a plain-text bank-tokens.json when you turn the password on
  • bank-tokens.refused.json, beside it, is owner-only (0600) too. It holds a one-way SHA-256 digest of each long-term token the importer retired, never the token, so it is not sealed
  • Four more files on the data volume are owner-only (0600) too: the import history (audit-log.json), OTP requests and their answers (otp-requests.<id>.json and otp-requests.<id>.answer.json), the OTP channel (otp-settings.json) and the push device tokens (devices.json). A file an earlier release left readable to others is made owner-only the next time it is read. The import history, the OTP channel and the device tokens are replaced in one atomic step on every write. An OTP request or answer appears whole under a name that can be taken only once and is not rewritten, except that a used code's answer is replaced, in one atomic step, by a marker. A damaged file is moved aside (<name>.quarantined-…) before the next write; an OTP file is not written again, so a damaged one is left in place. When the importer or the portal starts, it removes staged leftovers older than an hour from the files it writes, OTP requests and answers an hour past their deadline, and a damaged OTP file an hour after its last write
  • Consider using encrypted filesystem (LUKS, BitLocker, etc.)
  • On VMs, use encrypted EBS/disk volumes

Data in Transit

  • ✅ Bank scrapers use HTTPS
  • ✅ Actual Budget API uses HTTPS (if configured)
  • ⚠️ Ensure your Actual server uses HTTPS in production

🚨 Vulnerability Reporting

Reporting Security Issues

DO NOT open public GitHub issues for security vulnerabilities.

Instead, use GitHub Security Advisories:

  1. Go to the Security tab on the repository
  2. Click "Report a vulnerability"
  3. Include:
  4. Description of the vulnerability
  5. Steps to reproduce
  6. Potential impact
  7. Suggested fix (if any)

We will respond within 48 hours and work on a fix.

Security Updates

Security patches will be released as:

  • Critical: Immediate patch release (e.g., v1.0.1)
  • High: Within 1 week
  • Medium/Low: Next minor release

Incident Response

If a security vulnerability is discovered in a released version:

  1. Triage (within 24 hours): Assess severity and impact
  2. Containment: If critical, publish a GitHub Security Advisory immediately
  3. Fix: Develop and test a patch on a private branch
  4. Release: Ship as a patch release with fix: conventional commit
  5. Notify: Update the GitHub Security Advisory with fix details

🛡️ Best Practices

For End Users

  1. Keep Docker Image Updated
docker pull sergienko4/israeli-bank-importer:latest
  1. Use Strong Passwords
  2. Use unique passwords for Actual Budget server
  3. Enable 2FA on bank accounts where available

  4. Monitor Logs

docker-compose logs -f israeli_bank_importer
  • Check for failed login attempts
  • Check for unusual activity

  • Limit Network Access

  • Run on private network if possible
  • Use firewall rules to restrict outbound connections
  • Only allow connections to: banks, Actual server

  • Regular Security Audits

  • Review config.json periodically
  • Remove unused bank configurations
  • Check file permissions

For Developers

  1. Dependency Scanning
npm run audit
docker scan israeli-bank-importer:latest
  1. Code Review
  2. All PRs require review
  3. Security-sensitive changes require maintainer approval

  4. No Hardcoded Secrets

  5. Never commit credentials
  6. Use environment variables or volume mounts

  7. Principle of Least Privilege

  8. Request minimum Docker capabilities
  9. Run as non-root user
  10. Use minimal base image

🔍 Security Features

Current Security Measures

Container Security

  • ✅ Non-root container user (node, UID 1000)
  • ✅ Secure directory permissions (755)
  • ✅ Read-only application files (cannot be tampered)
  • ✅ Base image pinned to digest (supply chain protection)
  • ✅ Minimal base image (node:26-slim)
  • ✅ Health checks enabled (auto-restart on failure)
  • ✅ cap_drop: ALL + only SYS_ADMIN (least privilege)
  • ✅ no-new-privileges security option
  • ✅ Dedicated /dev/shm tmpfs for browser shared memory

Application Security

  • ✅ Config file not baked into image
  • ✅ Read-only config mount supported (:ro)
  • ✅ Credentials never logged
  • ✅ .gitignore prevents credential commits
  • ✅ Comprehensive input validation (UUIDs, URLs, dates, emails)

Dependency Security

  • ✅ npm run audit: blocking CI check (fails at MODERATE+). An advisory passes only if it is fixed, or carries an entry in config/audit-policy.mjs that states a non-empty reason and whose expires is a real calendar date still in the future. An entry covering a package that reaches the production tree is an accepted risk rather than a deferral, so it must additionally set productionReachable and noUpstreamFix, carry an added date no later than today, give an HTTPS upstream link to the work being tracked, and expire within 30 days of added. A development-tree entry may instead set autoExtend: { fixedIn }: from its expires date on, it stands only while a package in the committed lockfile declares a range that cannot reach fixedIn, and every audit run warns about it (see ADR-0004). An auto-extending entry is never honoured in the production tree.
  • ✅ Automated dependency updates via Dependabot (npm, Actions, Docker). Safe patch and minor updates merge on their own after every required check passes; majors, Docker minors and script-pin rotations wait for a human (see ADR-0002)
  • ✅ CodeQL static analysis on every PR
  • ✅ Latest security patches applied (Node.js 26, TypeScript 6)

Build Security

  • ✅ Digest-pinned base image (immutable, reproducible)
  • ✅ npm, npx, corepack and yarn removed from the runtime image, so their bundled dependency trees cannot carry unpatchable CVEs
  • ✅ Multi-stage security in CI/CD
  • ✅ Automated vulnerability scanning
  • ✅ OWASP Docker Security compliance: 9/10

Supply Chain Security

  • ✅ SBOM (Software Bill of Materials) attached to every GitHub Release (SPDX format)
  • ✅ Docker images scanned with Trivy on every PR (CRITICAL + HIGH, blocks merge)
  • ✅ Automated dependency updates via Dependabot (npm, GitHub Actions, Docker), with safe patch and minor updates auto-merged after CI
  • ✅ Weekly lockfile refresh updates transitive dependencies and applies npm audit fix changes within declared ranges, then auto-merges after CI unless a pin crossed a major
  • ✅ main is audited every four hours. An advisory behind an exact transitive pin gets an overrides floor, which is proved to clear it before the pull request opens. Aged same-line ^ floors auto-merge; the rest, including any raised >= override, wait for a human (see ADR-0003)
  • ✅ npm run audit runs as blocking CI check (no bypass; exceptions expire, and an opted-in development-tree exception outlives its date only while the committed lockfile blocks the fix)

📋 Security Checklist

Before deploying to production:

  • Set chmod 600 config.json
  • Mount config as read-only (:ro)
  • Use HTTPS for Actual Budget server
  • Enable 2FA on bank accounts
  • Run on private/secure network
  • Use encrypted filesystem/volumes
  • Regular Docker image updates
  • Monitor logs for anomalies
  • Backup config.json securely
  • Limit SYS_ADMIN to trusted images only

📚 Additional Resources


📄 License

This security policy is part of the Israeli Bank Importer project and is licensed under the MIT License.



🔄 Security Update History

v1.4.1 (2026-02-18) - Security Hardening

  • ✅ Base image pinned to digest for supply chain protection
  • ✅ OS security patches applied (apt-get upgrade)
  • ✅ npm updated from 10.x to 11.x (major security update)
  • ✅ TypeScript updated to 5.9.3
  • ✅ File permissions hardened (read-only application files)
  • ✅ Docker health checks added
  • ✅ Automated security scanning in CI/CD
  • ✅ npm audit: 0 vulnerabilities

v1.4.0 (2026-02-18) - Observability

  • ✅ Configuration validation at startup
  • ✅ Metrics collection for monitoring

v1.3.0 (2026-02-18) - TypeScript Migration

  • ✅ Full type safety
  • ✅ Error handling improvements

Last Updated: 2026-02-21