Skip to content

Commit 4280470

Browse files
committed
fix: dashboard decrypts masked_card + accept unknown CLI args + community discussion
1 parent 64b04c1 commit 4280470

File tree

2 files changed

+33
-1
lines changed

2 files changed

+33
-1
lines changed

CONTRIBUTING.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,15 @@ The `PopBrowserInjector` uses Playwright's `connectOverCDP` for cross-origin ifr
125125
- A brief note in the PR description confirming you verified the domain is controlled by the payment processor, not a reseller or affiliate
126126

127127
If you have an idea for a feature or a bug fix, please open an issue or submit a Pull Request!
128+
129+
## Open Discussion: masked_card Encryption
130+
131+
Currently, `masked_card` values (e.g., `****-4242`) are encrypted at rest in SQLite using AES-256-GCM. The dashboard API decrypts them before display.
132+
133+
We're seeking community input on whether this encryption is necessary:
134+
- **Current state**: Masked card values like `****-4242` are encrypted in `pop_state.db` and decrypted on read
135+
- **Argument for keeping**: Defense-in-depth — even masked data gets encryption
136+
- **Argument for removing**: `****-4242` is not PCI-sensitive data (PCI DSS explicitly allows truncated PAN display). Encryption adds complexity and caused a dashboard display bug where raw ciphertext was shown instead of the masked value
137+
- **Note**: Full card numbers are never stored in the database — only the masked form
138+
139+
If you have opinions on this, please open an issue or discussion.

dashboard/server.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,26 @@ def get_seals(self, status_filter=None):
125125
conn.close()
126126

127127
seals = [dict(row) for row in rows]
128+
129+
# Decrypt masked_card for display
130+
import hashlib, hmac, socket, base64
131+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
132+
try:
133+
enc_key = hmac.new(b"pop-pay-state-salt", socket.gethostname().encode(), hashlib.sha256).digest()
134+
for seal in seals:
135+
mc = seal.get("masked_card")
136+
if mc:
137+
try:
138+
data = base64.b64decode(mc)
139+
if len(data) >= 28:
140+
nonce, tag, ct = data[:12], data[12:28], data[28:]
141+
aesgcm = AESGCM(enc_key)
142+
seal["masked_card"] = aesgcm.decrypt(nonce, tag + ct, None).decode("utf-8")
143+
except Exception:
144+
pass # Already plaintext or corrupt
145+
except Exception:
146+
pass # cryptography not installed — show raw
147+
128148
self._set_headers()
129149
self.wfile.write(json.dumps(seals).encode())
130150

@@ -196,7 +216,7 @@ def main():
196216
parser.add_argument("--db", type=str, default=DEFAULT_DB_PATH, help=f"Path to SQLite database (default: {DEFAULT_DB_PATH})")
197217
parser.add_argument("--no-open", action="store_true", help="Do not open the browser automatically")
198218

199-
args = parser.parse_args()
219+
args, _ = parser.parse_known_args()
200220

201221
server = create_server(args.port, args.db)
202222
url = f"http://127.0.0.1:{args.port}"

0 commit comments

Comments
 (0)