How We Think About Security at Sourcepull
Most software companies treat security as a marketing line. This page is the answer instead — what we actually worry about, what we've done about it, when we did it, and what we haven't done yet.
Most software companies treat security as something you mention in a "Trust Center" link buried in the footer, with vague phrases like *we take your data seriously*. That's a marketing line, not an answer.
This page is the answer. It tells you what we actually worry about, what we've done about it, when we did it, and what we haven't done yet. If you're evaluating Sourcepull for your business, this is the page that should help you decide. We update it when something changes.
What we're protecting
Sourcepull is a small team building an AI-visibility audit tool for small businesses. We're not a bank. We're not storing medical records. The data we touch is intentionally minimal: your business name, your website, your city and category (all public information), your email address (used to deliver your audit, not sold or shared), and a score between 0 and 10.
We don't store passwords (we don't have accounts yet). We don't store payment cards (Stripe handles that — we never see a card number). We don't keep customer data we don't need.
That said: a small target is still a target. So we built the product around a clear threat model, and we revisit it.
Our threat model, in plain language
When we sit down to think about security, four things sit at the top of the list — in order of likelihood, not severity.
1. Someone tries to abuse our free tools to drain our AI credits
We pay real money every time someone runs a free signal check. If a bot hammered that endpoint, we'd burn through our Anthropic and Perplexity budgets before lunch. So every public endpoint that costs us money is rate-limited per IP, with tighter limits on the truly free tools and looser ones on the paid flow. The limits are backed by Redis when reachable and in-memory when not. We accept that determined attackers can rotate IPs; the goal is to make abuse expensive enough that it's not worth doing.
2. Someone tries to run a paid audit without paying
Each full audit costs us roughly fifty cents in real model spend. Every paid audit therefore requires a Stripe checkout session. We retrieve the session from Stripe directly, verify payment_status == "paid", and derive the tier band from the actual amount you paid — not from anything your browser sent us. We added a database-level uniqueness constraint so two simultaneous requests with the same Stripe session can't both spawn an audit. (Yes, that race condition existed once. We caught it in a code review and shipped the fix the same day. More on that below.)
3. Someone tries to compromise the operator account
We have an internal admin dashboard that lets the founder run audits, see system health, and manage the customer queue. Access is gated by a single token kept only in encrypted environment variables and the operator's browser local storage. Token comparisons happen in constant time, so an attacker can't time-attack their way to a guess. The token gets rotated on a schedule and any time we suspect exposure. We use a 256-bit cryptographically random value, not a memorable string. (We'd been using a 15-character hand-typed value early on. We caught that in our May 3 pentest, rotated it immediately.)
4. Someone tries to inject malicious content via customer inputs
Sourcepull takes a customer's brand name and other text inputs, sends them to AI models for analysis, and renders the result back as a report. A naive implementation could let a malicious customer plant content that compromises whoever views the report later. Customer-supplied strings get sanitized at the boundary (control characters stripped, lengths capped, special characters escaped). They get sanitized again before reaching any AI prompt (XML-tag-fenced so they can't break out of their container). And the report renderer enforces a strict scheme allowlist on links — only http, https, mailto, and tel URIs render as clickable. Anything else is collapsed to an inert anchor.
That's three independent layers for a single class of attack. Either of the first two would close the hole. All three is belt-and-suspenders, and it's deliberate: in security, redundancy is a feature, not a smell.
What we've actually done — the timeline
We don't list "industry standard practices" because most companies that say that mean *we run TLS*. Instead, here's what we've concretely done and when.
April 26, 2026 — Threat model documented
We wrote a deliberately short engineering doc covering what we worry about, what we don't worry about, what's in scope for review, and what's deferred. Internal artifact, but it's the foundation for everything below.
April 29, 2026 — Three-agent system audit
We ran three independent AI security agents (using a structured persona spec from public open-source security research) in parallel against the entire codebase: an AI-engineering reviewer, a security-engineering reviewer, and a data-remediation reviewer. They produced 54 findings between them.
The interesting result wasn't the count — it was that five findings were independently flagged by two or more reviewers in different vocabulary. That convergence is the highest-confidence signal you can get from a code review.
We shipped seven fixes the same session. SSRF protection was added to every endpoint that fetches a URL — outbound requests to private IP ranges (AWS metadata, RFC1918) are now blocked at the boundary, with explicit allow_redirects=False so an attacker can't bypass via a 302 redirect. We patched an X-Forwarded-For spoofing path so we now read client IP from x-real-ip (Railway's trusted header) rather than the spoofable header. We added a download-token gate so finished audit reports require a token to retrieve. We wrapped customer strings in XML delimiters before reaching AI prompts. We added a placeholder-leak scanner that hard-fails the pipeline if the AI accidentally leaves a literal [placeholder] in the customer's report. And we added arithmetic reconciliation on the score breakdown so the math gets verified before a report ships.
We tested all of this on production right after deploy. Confirmed: requests to 169.254.169.254 (AWS instance metadata) are blocked at the source.
May 3, 2026 — Three-agent penetration test
Six weeks of new features later, we ran another three-agent review — this one explicitly framed as a penetration test, with each agent assigned a different trust boundary (Python backend, Next.js frontend, cross-cutting auth/payment/infrastructure).
The result was 13 findings: zero critical, three high-priority, six medium-priority, and four informational. We closed five of them in a single commit on May 3.
The Stripe race condition got a partial database uniqueness constraint on session IDs so the same payment can never spawn two audits, even under concurrent requests. The "partial" part matters: internal admin runs are excluded from the constraint, so legitimate operator clicks can never falsely collide. Combined with a server-side conflict catch that re-queries and returns the existing audit on the rare race.
The operator-token exfiltration chain got closed two layers deep. Customer-supplied strings reaching the AI report are now sanitized at the prompt level, *and* the report renderer enforces a URI scheme allowlist. Either layer alone closes the hole. Two layers is on purpose.
The download token comparison switched from string equality to constant-time comparison, so a network attacker can't timing-attack their way to a token guess. Custom query inputs (when a customer edits the prompts the audit will run against AI platforms) now get the same boundary sanitization as every other customer input. And outbound email subject lines now have carriage-return and line-feed characters stripped, defending against an obscure header-injection vector.
The fix bundle shipped with 14 new automated regression tests (all green) and zero regressions in our existing test suite of 71 tests. We rotated the operator token the same day, before shipping anything else. Old token was 15 characters and hand-typed; new token is 256 bits of cryptographically random output.
Three remaining medium-priority items are on our backlog with explicit remediation plans: Content Security Policy headers in the web frontend, a redirect-handling refinement on one URL-fetch path, and migrating one Supabase code path from service-role to row-level-security. None are exploitable today; all are defense-in-depth improvements.
What's always on
Beyond the dated work above, these are practices we run continuously. We delete any customer data we can't justify keeping. No API keys live in source control — a pre-commit hook checks for the obvious leak patterns, and secrets stay in environment variables only. We keep operational discipline against screenshots that contain tokens. Stripe handles all card data, so we never see card numbers, never store them, never log them. Everything is encrypted in transit (TLS, no exceptions). All admin actions are logged so we can reconstruct who did what when, even six months later. Dependency CVEs are patched before new features, and the Next.js framework gets updated within 48 hours of any security advisory.
What we don't do (yet)
Honesty is the point of this page. Things we haven't done, why, and when we'll revisit.
We have not commissioned a paid third-party penetration test. A proper one runs $5,000–$15,000 for a small SaaS. At our pre-revenue stage, that money is better spent shipping the things customers are actually paying for. We re-evaluate this when we hit five paying customers, or whenever we start storing customer data we currently don't.
We do not have SOC 2 or ISO 27001 certification. Same reasoning. Useful for selling to enterprise; we sell to small businesses. We'll start the SOC 2 process when we have a customer who needs it, not before.
We do not run a formal bug bounty program. Hard to do responsibly without a triage pipeline. We'll start one when we have full-time security capacity.
We do not have a formal account system or per-user role-based access controls. We don't have user accounts yet. When we add them, this section gets longer.
We think honesty about what's missing is a stronger trust signal than a pile of certifications you can't verify.
How to report a security issue
If you find something, email us at hello@sourcepull.ca with "Security" in the subject line. We aim to acknowledge within one business day and to ship a fix on a timeline matched to severity. Critical issues (active exploit, customer data exposure) get a same-day patch with public disclosure within seven days. High-severity issues (no active exploit but real risk) get patched within seven days. Medium and low issues get patched in the next reasonable release window and noted in a future update of this page.
We don't currently pay bounties. We do publicly credit reporters who want credit, with their permission.
Why we publish this
A lot of security pages are about looking secure. This one is about being audited.
If you've read this far, you can ask sharper questions in a sales conversation than 95% of buyers do. We'd rather answer those questions than vague ones, because the answer is the same — what we built — and the conversation goes faster.
If you spot something we should add, change, or fix on this page, we want to hear it.
Curious how your domain scores against the same audit pipeline this study used? The free signal check takes about a minute.