Trust
Security at PaloWorks
Last updated Sep 19, 2026 · Ferrier Industries LLC
PaloWorks holds the commercial spine of your business: who your clients are, what you agreed to build, what they signed, and what they owe you. This page describes what we actually do to protect that, and — just as importantly — what we do not yet do. Every control below is traceable to code in the product.
Accounts and sessions
Authentication runs on Better Auth against our own Postgres database. We never see or store a password in a reversible form.
Passwords are hashed with scrypt
Each password is hashed with scrypt (N=16384, r=16, p=1, 64-byte output) under a unique 128-bit random salt. scrypt is memory-hard, which makes bulk offline cracking expensive rather than merely slow. Minimum length is eight characters and it is enforced on the server, not just in the browser.
lib/auth.ts, better-auth password hashing
Sessions live in the database, so revocation is immediate
A session expires seven days after it is issued and refreshes if it is more than a day old. Because every request validates the session against a database row rather than trusting a self-contained cookie, signing out or resetting a password takes effect at once instead of waiting for a token to expire.
lib/auth.ts, prisma/schema.prisma (Session)
A password reset invalidates every existing session
Reset links expire after one hour, and completing a reset revokes all sessions for that account. Someone who resets their password because they think they were compromised actually ends the intruder's access.
lib/auth.ts (resetPasswordTokenExpiresIn, revokeSessionsOnPasswordReset)
Sign-in attempts are rate limited
Ten sign-in attempts per fifteen minutes, twenty sign-ups per hour, and five password-reset or verification-email requests per fifteen minutes, counted per client. Tight enough to make credential stuffing impractical, loose enough that a shared office IP is not locked out.
lib/auth.ts (rateLimit.customRules), lib/rate-limit.ts
The signing secret must be set in production
The app refuses to boot in production without BETTER_AUTH_SECRET, because a generated-per-deploy secret would silently invalidate every session on each release and mask the misconfiguration.
lib/auth.ts
Workspace isolation
Your workspace is the security boundary. Nothing in the app reads or writes a record without proving it belongs to the workspace the signed-in user is in.
Every query is scoped to a workspace
The active workspace is resolved from the session on the server, and every database read and write is filtered by that workspace id. Workspace-level settings, billing, and team management additionally require the workspace owner.
lib/workspace.ts (getCurrentWorkspace, requireWorkspaceOwner)
Submitted record ids are never trusted
A form can be forged, so an id arriving in a request body is re-resolved through its verified parent before anything is written. Posting someone else's invoice id does not reach someone else's invoice.
The limit of this: This is enforced by code convention and review rather than by database row-level security. It is a real control, but it is a discipline, not a wall.
app/(app)/**/*-actions.ts
Submitted values are validated before they reach the database
Form input passes through hand-written typed readers that enforce length, format, and range, strip control characters that Postgres rejects or that render as invisible junk on an invoice, and read money strictly as integer cents. Queries are parameterised by Prisma; the app builds no SQL from strings.
lib/validation.ts, lib/prisma.ts
Payments
Card data never touches our servers. Stripe handles it end to end, and client payments settle into your own Stripe account rather than ours.
We never receive card numbers
Card entry happens in Stripe's own hosted interface. What we store is a Stripe customer id, a subscription id, and a connected account id — references, not instruments. There is no card number, CVC, or bank account number in our database, and no schema field that could hold one.
prisma/schema.prisma (Workspace), app/api/stripe/*
Client payments go to your Stripe account, not ours
Invoice payments are charged directly on your connected Stripe account, with no PaloWorks fee taken. Your clients' money is never held in a PaloWorks balance and never passes through our books.
lib/stripe-connect.ts
Webhooks are signature-verified and replay-safe
Every Stripe webhook is verified against the endpoint signing secret before it is acted on, and each event id is recorded so a retried delivery cannot apply the same change twice.
app/api/stripe/webhook/route.ts, prisma/schema.prisma (ProcessedStripeEvent)
PCI scope
Stripe is a PCI DSS Level 1 service provider. Because cardholder data never reaches our systems, PaloWorks itself is outside PCI scope rather than certified within it — an important distinction, and one worth stating plainly.
app/api/stripe/*
Transport and browser hardening
Security headers are set on every response by the application rather than left to the host, so they hold even behind a proxy that rewrites headers.
HTTPS is enforced for two years, including subdomains
Strict-Transport-Security is sent with max-age=63072000, includeSubDomains, and preload, so a browser that has seen the site once will refuse to connect over plain HTTP afterwards.
next.config.ts
A Content Security Policy restricts where the page can talk
default-src is limited to our own origin. The browser may connect only to our own origin (and our file-storage endpoint, for uploads), framing is forbidden outright (frame-ancestors 'none' plus X-Frame-Options: DENY), plugins are blocked with object-src 'none', <base> is locked to our own origin, and forms may submit only to our own origin and Stripe Checkout. Pages you sign in to and client documents get a fresh script nonce on every request, with 'strict-dynamic', so an inline script without that nonce does not run.
The limit of this: Prerendered public pages (the marketing site, sign-in pages, pricing) are served as static files that cannot carry a per-request nonce, so their script-src still permits 'unsafe-inline'. 'unsafe-eval' is allowed only in local development. Treat our CSP as defence in depth rather than as XSS protection on its own.
lib/content-security-policy.ts, proxy.ts
MIME sniffing, referrers, and device APIs are constrained
X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin so full URLs (which contain share tokens) are not leaked to third-party sites, and a Permissions-Policy that denies camera, microphone, and geolocation to the whole application.
next.config.ts
Abuse limits on public endpoints
Publicly reachable writes — intake submissions, contract signing — are limited to fifteen per ten minutes per client, and expensive operations such as workspace or checkout creation to twenty per hour. State lives in Postgres so the limit holds across serverless instances rather than resetting with each one.
The limit of this: The limiter deliberately fails open: if the database check errors, the request is allowed through. We chose availability over lockout, which means a database outage also degrades rate limiting.
lib/rate-limit.ts
Audit and accountability
Once a workspace has more than one person in it, "the invoice was marked paid" stops being a useful answer to "by whom?".
Money and access changes are recorded
Invoice creation, deletion, and status changes; scope updates; contract sharing and signing; team member removal; invite creation and revocation; plan changes; payment-account status changes; and share-link regeneration are each written to an append-only workspace log with the actor's id, their email, and a timestamp.
The limit of this: The log is deliberately narrow — it covers actions that move money or change who can see what, not every read or page view. It is an accountability trail for your team, not a full system log, and it is not tamper-evident.
lib/audit.ts
Signed contracts carry their own evidence
A signature records the typed name, the timestamp, the signer's IP address and browser user agent, and a SHA-256 hash of the exact contract text at the moment of signing. The hash is re-checked on every view, so a contract edited after signature is flagged rather than silently accepted.
lib/contract-signing.ts, prisma/schema.prisma (Contract)
What we don’t claim
Read this as the rest of the page. Your security reviewer will ask about every one of these, and the cheapest answer is to have told you first.
- SOC 2, ISO 27001, or HIPAA
- PaloWorks holds none of these. There is no audit in progress and no report to send you. If your procurement process requires one, we are not yet the right vendor and we would rather say so now.
- Third-party penetration testing
- No external penetration test or security audit has been performed. Our controls have been reviewed by the people who wrote them, which is worth something but is not an independent assessment.
- Encryption at rest
- Data is encrypted in transit with TLS on every connection. At rest, we rely on whatever full-disk and volume encryption our hosting and database providers apply by default. We have not independently verified it, we do not manage the keys, and there is no application-level or per-field encryption. So we describe it rather than guarantee it.
- Single sign-on (SSO)
- Not available. There is no SAML or OIDC single sign-on and no SCIM provisioning. Two-step verification with an authenticator app and passkeys are available, and a workspace owner can require two-step verification for every member.
- A bug bounty programme
- We do not pay for vulnerability reports. We do read every one of them and we will credit you if you want the credit. See responsible disclosure below.
- A 24/7 security team
- PaloWorks is a small operation. There is no follow-the-sun on-call rotation. Reports and incidents are handled in business hours, promptly, by people who can actually change the code.
- Tested disaster recovery
- Backups are those our database provider takes automatically. We have not run a documented restore drill, so we cannot quote you a tested recovery time or recovery point objective. Export your data regularly; the app supports it.
- Password-protected share links
- A share link is a bearer credential: anyone who has the URL can open the document, with no password and no login. That is the point — your clients should not need an account — but it means a forwarded link is a shared document. Regenerate the link to cut off access.
Subprocessors
The third parties that can touch data you put into PaloWorks. Entries marked conditional are active only where the relevant integration is configured; we list them either way so this page is accurate on every deployment.
Vercel Inc.
In useApplication hosting, serverless execution, and CDN delivery.
- Data handled:
- All data transiting the application, plus request logs containing IP addresses and URLs.
- Processed in:
- United States, with global edge delivery
Neon Inc. (managed PostgreSQL)
In usePrimary database.
- Data handled:
- Everything the product stores: accounts, workspaces, clients, projects, scopes, contracts, invoices, payments, time entries, and audit records.
- Processed in:
- United States
Stripe, Inc.
In useSubscription billing, and Stripe Connect payment processing and payouts for your client invoices.
- Data handled:
- Cardholder data (which it collects directly, never through us), billing contact details, payout and identity information you provide during Connect onboarding, and invoice amounts.
- Processed in:
- United States and Ireland
Resend (Plus Five Five, Inc.)
ConditionalTransactional email: invoice and scope share links, reminders, team invitations, password resets, and verification emails.
- Data handled:
- Recipient email addresses and the contents of the messages we send.
- Processed in:
- United States
Active when an email API key is configured. Without one, emails are written to the server log instead of being sent, and no data leaves our infrastructure.
PostHog, Inc.
ConditionalProduct analytics — which features get used, and where people get stuck.
- Data handled:
- Event names and a workspace identifier. No client names, project contents, invoice amounts, or email addresses are sent.
- Processed in:
- United States
Active when an analytics key is configured. Without one, events are discarded before any network call is made.
Google LLC
ConditionalOptional "Sign in with Google" authentication.
- Data handled:
- Your email address and name, received from Google when you choose to use it.
- Processed in:
- United States
Active only where Google sign-in is configured, and only for accounts that choose it. Email-and-password accounts never touch Google.
Anthropic PBC
ConditionalAI-assisted drafting — suggesting scope deliverables, pricing anchored on your own past work, and draft client messages.
- Data handled:
- Only the workspace content needed for the specific request: project and scope text, intake answers, and invoice amounts. Pricing history is sent with client names stripped. No AI request is made unless you trigger one.
- Processed in:
- United States
Active only where an AI key is configured and on plans that include the AI features. Without a key the AI layer reports itself as unavailable and no request leaves our infrastructure. AI suggestions are never written to your records automatically — a person has to apply one.
We notify workspace owners by email at least 30 days before adding a subprocessor. See the Data Processing Addendum for your right to object.
What we keep, and for how long
Described from the database’s actual deletion rules, not from intent.
| Data | Retention | What deletion removes |
|---|---|---|
| AccountYour email address, name, scrypt password hash, and any linked Google account. | For as long as your account exists. | Deleting your workspace signs you out and ends your access. Ask us to close the account itself and we remove the account record and its linked sign-in methods. |
| SessionsSession tokens, plus the IP address and browser recorded when each was issued. | Seven days from issue, refreshed while you stay active. Removed immediately on sign-out, on password reset, and on workspace deletion. | Removed immediately. |
| Workspace business recordsClients, projects, intake responses, scopes and deliverables, change orders, revisions, tasks, comments, attachments, time entries, invoices and their line items, recurring invoices, payment records, and your project and contract templates. | For as long as the workspace exists. | Deleted immediately and irreversibly when the workspace is deleted, by database cascade. There is no recycle bin and no undo. Export first. |
| Signed contracts and signature evidenceContract text, the signer's typed name, signature timestamp, signer IP address and browser, and the SHA-256 hash of the signed text. | For as long as the project the contract belongs to exists. | Deleted with the project or the workspace. A signed contract is frequently needed after the work has ended — download the PDF before you delete anything. |
| Audit logAction name, the record it affected, the acting user's id and email, and a timestamp. | For as long as the workspace exists. | Cleared when the workspace is deleted, with one deliberate exception described below. |
| Deletion tombstoneA single row recording that a workspace was deleted: the workspace id and name, how many clients, projects, and invoices went with it, the email of the owner who confirmed it, and the timestamp. | Kept indefinitely. It is how we answer "what happened to my data?" months later, and how we can show a deletion was authorised. | Not removed by workspace deletion. It contains no client names, project contents, contract text, or financial detail — only counts. Email us and we will remove it on request. |
| AI prompts and suggestionsThe workspace content sent to the model for a request you triggered, and the suggestion it returned. | The prompt we sent and the response that came back are kept with a record of the request for 30 days, long enough to finish and review a draft, and then the text is erased. The record itself (the kind of request, whether it succeeded, and when) is deleted after a year. A suggestion becomes an ordinary record in your workspace only when you choose to apply it. We also count AI calls, for your plan's usage limit. | Deleting your workspace or closing your account deletes these records with it. Once a request has been sent to our AI subprocessor, its own handling is governed by its terms rather than by ours. |
| Payment and billing data held by StripeCards, charges, payouts, and the identity information Stripe collects for Connect onboarding. | Set by Stripe and by financial record-keeping law, not by us — typically several years. We hold only the Stripe identifiers, which we delete with your workspace. | Deleting your PaloWorks workspace does not delete anything in Stripe. Close or delete your Stripe account through Stripe. |
| Operational logsHosting request logs and error traces, containing IP addresses, URLs, and timestamps. | Retained by our hosting provider on its own schedule, typically around 30 days. | Not individually deletable — these expire on the provider's rolling schedule rather than on request. |
| Rate-limit countersA hashed key derived from an IP address and a request count. | The length of the window, up to one hour, then swept away. | Expires on its own; nothing to delete. |
Responsible disclosure
Found something? Tell us before you tell anyone else, and we will treat you well.
A person who works on the code reads every report. There is no autoresponder.
What we promise you
- A person who works on the code reads every report and answers it by email.
- We will tell you how serious we judge it to be and what we intend to do about it.
- We will not pursue legal action over good-faith research that stays within the boundaries below, and we will not ask your hosting provider to act against you.
- We will credit you publicly when the fix ships, if you want to be credited.
In scope
- The PaloWorks web application and its API.
- Authentication, session handling, and workspace isolation.
- Client-facing share links for invoices, scopes, intake forms, and contracts.
Out of scope
- Denial of service, volumetric testing, or anything that degrades the service for other people.
- Social engineering of our team, our customers, or their clients.
- Findings against our subprocessors' own systems — report those to Stripe, Vercel, Neon, or Resend directly.
- Reports produced solely by an automated scanner with no demonstrated impact.
- Missing headers or configuration hardening with no exploitable consequence.
Ground rules
- Use only accounts and data you own. Never access, modify, or retain another customer's data — if you stumble into it, stop and tell us what you saw.
- Give us a reasonable window to fix an issue before you publish it.
Questions this page does not answer? Email security@paloworks.com. See also our Privacy Policy, Data Processing Addendum, and Terms of Service.