Skip to content
kayadibi1Public

About

Agent-safety study: autonomous purchase agent with hard-coded spend caps and an HMAC confirmation gate that config cannot override

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

gpuwatch

RTX 5090 monitoring and auto-checkout tool for personal use on Windows 11.

41 security invariant tests passing

Why this is here (for reviewers)

This is an agent-safety study: an autonomous agent that can spend money, engineered so that it provably will not misbehave. The point is the constraint architecture, not the GPU.

  • Config-proof hard caps. Quantity 1, plus per-transaction and 24-hour spend ceilings, enforced as pure functions. They cannot be raised by configuration. Raising them requires a source change a reviewer would see.
  • An unforgeable confirmation gate. The one irreversible action (place-order) is reachable only through an HMAC capability token whose constructor is module-private, so no adapter can forge one.
  • A security test suite (tests/security/) whose design assumption is that every bypass must require a reviewable source change. Twelve invariants, each mapped to where it is enforced (table below).
  • No anti-bot evasion. It does not bypass CAPTCHAs or 2FA, and uses no stealth, proxy, or fingerprint-spoofing. On a challenge it stops and asks the human.

Honest status: detection ran for about 10 days in dry-run. The money-spending methods (add_to_cart, place_order) are deliberately left as NotImplementedError stubs marked TODO: USER in every adapter, so the agent never actually purchased anything. The full safety apparatus is real and tested against a fake adapter. The stub is deliberate, and the safety architecture is the deliverable.


LEGAL / ETHICAL NOTES — READ FIRST

This tool is provided for one person's legitimate, personal-use purchase of a single graphics card. Using it for scalping or resale is not permitted and is actively prevented by the safety invariants below.

  • Personal use only. This automates your logged-in browser session against your saved card and shipping address to buy one GPU you want to own.
  • Hard caps are enforced in code:
    • Quantity per order: exactly 1.
    • Per-transaction price cap: $2,800 USD.
    • Per-24-hour rolling spend cap: $3,000 USD.
    • Any listing exceeding these caps is rejected pre-click.
  • Vendor Terms of Service. Automated ordering may violate retailer ToS. You accept the risk of order cancellation, account suspension, or permanent ban. This tool does not attempt to evade anti-bot protection and does not use stealth / fingerprint-spoofing / UA-rotation / proxy infrastructure.
  • No resale. Don't use this to stockpile cards. The one-unit / spend-cap combo makes that hard by design; please don't try to route around it.
  • Taxes are your problem. Report any income from the ultimate use of what you buy.
  • You are responsible for everything this tool does while it runs as you. Read the safety invariants below and make sure you understand them before enabling --no-dry-run.
  • CAPTCHAs are not bypassed. When a retailer shows a CAPTCHA or 2FA challenge, the tool stops, notifies you, and asks you to complete it manually. It does not attempt to solve them, forward them to a solver service, or otherwise evade them.

If any of the above makes you uncomfortable, do not use this tool.


System requirements

  • Windows 11 (tested on 11 Pro, build 26200+).
  • Python 3.12+ with uv installed.
  • Chromium via Playwright (uv run playwright install chromium).
  • A Telegram bot account (for interactive CONFIRM / CANCEL prompts) or an SMTP-capable email account (fallback).
  • A vendor account at each retailer you enable, with:
    • A saved payment method (the card whose last-4 matches [payment]).
    • A saved shipping address (matching [ship_to]).
    • "Remember me" / session-persistence enabled.

The tool runs as a logged-in foreground user. It cannot run under LocalService or unattended on a locked machine (Playwright needs an interactive session).


Setup

git clone <this-repo> gpuwatch
cd gpuwatch
uv sync --all-extras
uv run playwright install chromium

Walk through the secret-setup prompt. Secrets are stored in the Windows Credential Manager via keyring, not in any file:

uv run gpuwatch setup-secrets

You'll be prompted for:

  • Telegram bot token + chat ID (optional — skip to use email-only).
  • SMTP host, port, username, password, from-address, to-address (the fallback channel; strongly recommended).
  • SQLCipher database passphrase (can be any non-empty string — it encrypts data/gpuwatch.db).

Sanity-check everything is wired:

uv run gpuwatch test-notify

Expected output:

telegram: OK
email: OK

If either side reports FAIL/unconfigured, re-run setup-secrets for that channel.


config.toml

Non-secret configuration lives at ./config.toml (project root). The defaults ship safe: dry_run_default = true, all vendors enabled = false.

Required edits before anything useful happens:

[general]
timezone = "America/Los_Angeles"
dry_run_default = true         # stays here; CLI flag overrides

[ship_to]
name            = "Firstname Lastname"
street_number   = "1234"       # just the number; used for equality check
zip             = "94107"

[payment]
card_last4      = "4242"
card_exp_month  = 4
card_exp_year   = 2028
card_brand      = "visa"       # visa / mastercard / amex / discover

[scheduler]
min_poll_seconds    = 45
max_poll_seconds    = 180
absolute_min_seconds = 30       # hard floor — do not lower

[notifications]
primary  = "telegram"
fallback = "email"

[observability]
prometheus_enabled   = false
prometheus_port      = 9090
daily_summary_hour   = 9

[vendors.bestbuy]
enabled                = true
full_auto_after_first  = false  # stays false until you explicitly flip it
product_urls = [
  "https://www.bestbuy.com/site/nvidia-geforce-rtx-5090-founders-edition/XXXXXXX.p",
]

# ... repeat for each vendor you want to watch

Every vendor block takes enabled, full_auto_after_first, and product_urls (a list). Other per-vendor knobs may be added in future phases. Leave a vendor disabled if you don't have a working first-party login for it.


One-time manual vendor login

Before gpuwatch run can cart anything for a given vendor, you must log in once by hand so the persistent browser context has a session cookie:

  1. Launch a headful browser session pointing at the vendor's home page using the gpuwatch-managed profile directory (under sessions/<vendor>/). A helper will be added in a follow-up phase; for now:
    uv run python -c "from playwright.sync_api import sync_playwright; p=sync_playwright().start(); b=p.chromium.launch_persistent_context(user_data_dir='sessions/bestbuy', headless=False); b.new_page().goto('https://www.bestbuy.com/'); input('press enter after you have logged in and saved your card...')"
  2. Log in with your real account.
  3. Add your payment card to "saved cards" and confirm "set as default".
  4. Add / confirm your shipping address.
  5. Tick "Remember me" / "Keep me signed in".
  6. Close the browser (or hit enter in the terminal helper above).

Repeat for each vendor you enable. The cookies / local storage live in sessions/<vendor>/ and are required for the cart flow.


Running

Default mode is dry run. This exercises the entire pipeline — detection, condition verification, matching, notification, confirmation gate, pre-click verification, screenshot capture — but stops just short of clicking "Place order". Instead it records an abandoned purchase attempt with failure_reason = "dry-run".

uv run gpuwatch run                   # dry-run
uv run gpuwatch run --dry-run         # explicit dry-run

Exercise the notification pipeline without any live vendor traffic:

uv run gpuwatch simulate-instock --vendor bestbuy

This uses tests/fixtures/bestbuy/in_stock_5090_fe.html (or the fixture you pass via --fixture PATH) to synthesize an in-stock event, then runs it through the confirmation gate. Expect a Telegram / email with CONFIRM / CANCEL buttons.


Going live — the two-flag ritual

To actually spend money you must pass both flags:

uv run gpuwatch run --no-dry-run --i-understand-this-will-spend-real-money

Passing only --no-dry-run exits with code 2 and a reminder. This is intentional. Do not alias it away.

Recommended progression:

  1. Run for 24–48h in default dry-run mode. Watch the logs + daily summary under logs/daily-YYYY-MM-DD.md.
  2. simulate-instock once per enabled vendor. Confirm you receive the CONFIRM/CANCEL prompt and the confirmation coordinator reacts correctly.
  3. Once you trust it, flip --no-dry-run --i-understand-this-will-spend-real-money.
  4. The first purchase per vendor always pauses for human confirmation regardless of full_auto_after_first. Only after a successful first order with that vendor (and you manually flipping full_auto_after_first = true in config) will subsequent checkouts skip the human gate.

Scheduled auto-start (Windows Task Scheduler)

A template is included at deploy/gpuwatch.xml. It fires on boot (30s delay) and user logon, runs as the interactive user at least-privilege, and restarts on failure up to 3x per minute.

Import it via:

schtasks /Create /TN "gpuwatch" /XML "C:\path\to\gpuwatch\deploy\gpuwatch.xml"

Edit the <Command> and <WorkingDirectory> paths first if your install lives elsewhere. The task XML intentionally does not pass --no-dry-run; add that only once you trust the setup.


Adding a new vendor

Every vendor is a small module under src/gpuwatch/vendors/<name>/:

  • __init__.py — empty.
  • selectors.yaml — five keys: title, price, availability, sold_by, breadcrumb. CSS selectors, one per key.
  • adapter.py — subclass of VendorAdapter:
    • name class attribute (string slug).
    • check_stock() (Phase 2 pattern: fetch each URL, parse via parse_product_page, return CheckResult).
    • add_to_cart, proceed_to_review, place_order, watch_order — start as NotImplementedError with TODO: USER markers; fill in once you have a working manual login.
  • Fixtures under tests/fixtures/<name>/:
    • in_stock.html + out_of_stock.html minimum. Add open_box.html / refurbished.html etc. as the retailer has them.
  • Unit test at tests/unit/vendors/test_<name>.py — copy an existing vendor's test file and swap the paths.

Finally, add your vendor to register_all_adapters() in src/gpuwatch/vendors/registry.py and add a [vendors.<name>] block to config.toml.

The Micro Center adapter (src/gpuwatch/vendors/microcenter/) is the simplest complete example.


Safety invariants

These are enforced in code and verified by the tests/security/ suite. Attempting to bypass any of them is considered a bug.

# Invariant Where enforced
1 Quantity per order is exactly 1 core.pre_click_verifier + core.matcher
2 Per-transaction price cap: $2,800 core.pre_click_verifier
3 Per-24h rolling spend: $3,000 core.pre_click_verifier (DB sum)
4 Only NEW, first-party listings accepted core.condition_verifier
5 Marketplace / warehouse / renewed / open-box rejected core.condition_verifier (deny substrings + allowlist)
6 Card last-4 + brand + expiry must match [payment] before place-order core.pre_click_verifier
7 Shipping name + street number + ZIP must match [ship_to] core.pre_click_verifier
8 Confirmation token (CONFIRM/CANCEL) required on first purchase per vendor core.confirmation_coordinator
9 --no-dry-run requires --i-understand-this-will-spend-real-money cli.run
10 Per-attempt screenshot captured before and after place-order click core.screenshot
11 UNKNOWN-state attempts cannot auto-resume; must be cleared via reset-hold core.attempt_state_machine + cli.reset-hold
12 Secrets never on disk or in env vars — keyring only gpuwatch.secrets

Troubleshooting

CAPTCHA appears mid-checkout. The tool detects cart/checkout pages that look like a CAPTCHA challenge, notifies you via the primary channel, and halts that attempt with failure_reason = "captcha". Complete the challenge by hand in the paused browser window, then gpuwatch reset-hold <attempt_id> to clear the hold.

Order stuck in UNKNOWN. Happens when the place-order click was submitted but the response parse failed (network hiccup, unexpected DOM). The attempt stays in UNKNOWN forever until a human decides. Use:

uv run gpuwatch reset-hold <attempt_id> --confirm-no-duplicate

Only pass --confirm-no-duplicate after logging in and visually verifying no order was actually placed.

Notifications aren't arriving. Run uv run gpuwatch test-notify. If Telegram fails, verify your bot token and chat ID (chat ID is the numeric id of the direct-message chat with the bot, not your username). If email fails, most SMTP providers require an app-specific password; Gmail needs an "App Password" under 2FA.

Prometheus metrics. Set [observability] prometheus_enabled = true + prometheus_port = 9090 in config.toml. Scrape http://localhost:9090/metrics from your Prometheus server. Metrics: gpuwatch_checks_total, gpuwatch_check_latency_seconds, gpuwatch_in_stock_events_total, gpuwatch_near_misses_total, gpuwatch_purchase_attempts_total, gpuwatch_errors_total.

Daily summary. Written to logs/daily-YYYY-MM-DD.md at daily_summary_hour local time. Covers observations (with in-stock count), attempts grouped by vendor / state, and per-attempt detail with failure reasons.

Order-status watcher. After a SUCCEEDED attempt, an entry is inserted in order_watches and the watcher polls that vendor's order page every 30 min. On cancelled / unable_to_fulfill, detection resumes automatically for that listing. On delivered, the watch is retired.


Development

uv sync --all-extras
make test         # unit + integration
make security     # invariant-bypass tests + bandit + pip-audit
uv run pytest -v  # full run

Design spec: docs/superpowers/specs/2026-04-19-gpuwatch-design.md. Phase plans: docs/superpowers/plans/.


Status

  • Phase 1 — foundation, settings, secrets, DB schema — complete.
  • Phase 2 — HTTP detection, three vendor adapters, condition verifier — complete.
  • Phase 3 — scheduler, notifications, confirmation coordinator, simulate — complete.
  • Phase 4 — pre-click verifier, screenshot, checkout orchestrator, security suite — complete.
  • Phase 5 — remaining vendors, order watcher, Prometheus, daily summary, deploy — complete.

Post-Phase-5 TODO for the user:

  1. Paste real product-page HTML into each tests/fixtures/<vendor>/*.html (current fixtures are minimal placeholders).
  2. Run gpuwatch setup-secrets to populate the Windows Credential Manager.
  3. Edit config.toml — [ship_to], [payment], each [vendors.<name>] product_urls and enabled.
  4. Do one-time manual login per vendor (launch headful browser, log in, save card + address, check "remember me").
  5. Run gpuwatch test-notify.
  6. Run gpuwatch simulate-instock --vendor bestbuy and confirm the CONFIRM/CANCEL prompt arrives.
  7. Let gpuwatch run (dry-run) soak for 24–48h.
  8. Flip to --no-dry-run --i-understand-this-will-spend-real-money once everything is boring.

About

Agent-safety study: autonomous purchase agent with hard-coded spend caps and an HMAC confirmation gate that config cannot override

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages