SwiftInvoicePro — Security Test Scope
Operator: Apex Flow Technology Ltd Environment under test: the isolated synthetic environment at /security-testing Contact: natromrich@googlemail.com Document status: self-authored scope, issued with each engagement key
0. How to read this document
This is the written scope a customer security team, or a testing firm they retain, can work from without a call. It covers ten test areas. Each area states what is in scope, what the control is meant to do, how to exercise it against the synthetic environment, and what counts as a finding.
The target is a self-contained environment seeded with invented hotel data. It holds no production records and has no path to the production database. Destructive testing is expected; POST /api/public/pentest/reset restores the seed state at any point.
Base URL: https://swiftinvoicepro.com Engagement key: issued per test, time-boxed to the agreed window, sent in x-pentest-key. Synthetic tenants: ten_alpha (Northgate Riverside Hotel), ten_beta (Cavendish Court Hotel). Roles: viewer (read only), finance (may void an invoice).
Obtain a bearer token:
POST /api/public/pentest/session
x-pentest-key: <engagement key>
content-type: application/json
{ "tenantId": "ten_alpha", "role": "viewer", "ttlSeconds": 3600 }1. Public API surface and input handling
In scope. Every route under /api/public/pentest/*, plus the unauthenticated public endpoints of the product itself: /api/public/sandbox/aggregate, /api/public/sandbox/trips, /api/public/contact, /api/public/interest, /api/public/pilot-request.
Intended behaviour. All bodies are schema-validated before use. Unknown fields are ignored rather than trusted. Batches are size-capped. Errors are generic and typed (bad_request, unauthorized, forbidden, not_found) and never carry stack traces, file paths, SQL fragments, or library versions.
How to test. Type confusion (string where number expected, arrays for scalars, nested objects for enums), oversized bodies, deeply nested JSON, duplicate JSON keys, unicode normalisation and homoglyphs in identifiers, null bytes, prototype-pollution keys (__proto__, constructor, prototype), wrong or missing content-type, chunked and truncated bodies, and method confusion (GET where POST is expected and the reverse).
A finding is. Any 5xx, any stack trace or internal identifier in a response body, any input that bypasses validation and reaches the data store, any parser difference that lets two components read the same body differently.
2. Authentication and token handling
In scope. The engagement key check, session issuance, and the signed bearer token.
Intended behaviour. The key is compared in constant time. Tokens are HMAC-SHA256 over the claim body with a server-side secret, carry tenantId, role and exp, and are rejected on any signature mismatch, malformed structure, unknown tenant, invalid role or past expiry. There is no alg field and no unsigned form.
How to test. Strip the signature; substitute an empty signature; re-sign with a guessed or empty secret; swap the claim body while keeping a valid signature from another token; splice signature and body between two legitimately issued tokens; extend exp and re-encode; send a token with role absent, null, or an unexpected string; send base64 padding variants and case changes; replay a captured token after expiry; probe for timing differences on the key check across a byte-varied corpus.
A finding is. Any token the server accepts that was not minted by it, any accepted token whose claims differ from those issued, acceptance past exp, or a measurable timing oracle on key or signature comparison.
3. Tenant isolation and IDOR
In scope. GET /api/public/pentest/invoices, GET|POST /api/public/pentest/invoices/{id}.
Intended behaviour. The tenant is derived from the signed token only. No query parameter, header, body field, cookie or path segment can widen or change it. A record belonging to the other tenant returns 404 not_found — not 403 — so the response does not confirm existence across the boundary. Related guest and room records are re-scoped to the token tenant before being joined, not trusted from the invoice.
How to test. With a ten_beta token, request every ten_alpha invoice id. Attempt override via ?tenantId=, ?tenant=, X-Tenant-Id, X-Forwarded-Tenant, a tenantId field in the POST body, duplicated parameters, array parameters, and mixed-case variants. Try path traversal and encoding on the id segment (..%2f, double-encoded, overlong UTF-8, trailing dot, unicode digits). Compare response times and body lengths between a non-existent id and a real cross-tenant id to look for an existence oracle.
A finding is. Any cross-tenant record content, any 403-vs-404 or timing difference that distinguishes "exists elsewhere" from "does not exist", or any join that leaks a guest or room from the other tenant.
4. Authorisation and privilege escalation
In scope. The finance-only void: POST /api/public/pentest/invoices/{id} with { "action": "void" }.
Intended behaviour. The role comes from the token claim and is checked before the body is read. viewer receives 403 forbidden and no state changes. No other action verb is accepted.
How to test. Call the void with a viewer token. Attempt method override headers (X-HTTP-Method-Override, _method), alternative content types (form-encoded, multipart, text/plain), an array or object as action, case and whitespace variants of "void", and a second undocumented verb. Attempt the write through the GET handler. Check whether a rejected call still mutated the record by re-reading it.
A finding is. Any state change from a viewer token, any acceptance of an undocumented action, or a partial mutation preceding a 403.
5. Webhook security — signing, tampering and replay
In scope. POST /api/public/pentest/webhook, and by extension the production ingest it mirrors.
Intended behaviour. HMAC-SHA256 over ${timestamp}.${nonce}.${rawBody} using the tenant's signing secret, constant-time comparison, a ±5-minute timestamp window, and a single-use nonce. The tenant named in the body must equal the tenant whose secret signed the request. Both synthetic tenants' secrets are issued with the engagement key deliberately, so cross-tenant signing can be attempted.
How to test. Replay a valid request verbatim. Reuse the nonce with a fresh timestamp and the reverse. Alter the body after signing. Sign a ten_beta body with the ten_alpha secret. Shift the timestamp to the edges of the window and beyond, and send negative, floating-point, millisecond and non-numeric timestamps. Truncate the signature; send it with different case or encoding; send an empty signature; omit the header entirely. Exploit delimiter ambiguity by moving a . between the timestamp, nonce and body components. Race two identical requests concurrently to test nonce single-use under concurrency.
A finding is. Any accepted request that was not correctly signed by the matching tenant, any successful replay, any tampered body accepted, or a race that lets one nonce be used twice.
6. Stored credential handling and logging
In scope. The credential-handling design as documented, tested here through observable behaviour rather than production data.
Intended behaviour. PMS credentials are encrypted at rest with a key held only as a server-side secret, decrypted only inside a server handler at call time, never rendered to a client, never returned by an API, and never written to logs, error bodies or telemetry. Secrets are never present in the browser bundle.
How to test. Search the served JavaScript bundles, source maps, HTML, and JSON responses for key material, token fragments, connection strings and the strings SUPABASE_SERVICE, ENCRYPTION, SECRET, PENTEST_. Force error paths and inspect responses for credential echo. Inspect every response header for internal identifiers. Confirm no endpoint returns a stored credential in any form, including truncated or hashed.
A finding is. Any secret, key fragment, or credential reachable from the client, in any response, or in a source map.
7. Business logic — pilot terms, invoicing and checkout
In scope. The invoice state machine in the synthetic environment, plus the production pilot acceptance flow (/pilot/start, POST /api/public/pilot-request) and the checkout routes, tested for logic rather than data.
Intended behaviour. An invoice cannot be voided twice, cannot move backwards from a terminal state, and totals must equal subtotal plus VAT. Pilot submission requires explicit consent and acceptance of the canonical terms version; the server validates and records the version and exact text, and does not trust a client-supplied version string. No credentials are issued by any automated path.
How to test. Void an already-void invoice; void concurrently from two sessions; submit a late charge against a void invoice; submit amounts that are negative, zero, fractional beyond two places, or large enough to overflow. Submit a pilot request with termsAccepted true but consent absent, with a forged or older terms version, with the version field removed, and with a modified terms body. Attempt to reach any credential-issuing or approval action directly.
A finding is. Any inconsistent total, any double-void or backwards transition, any accepted acceptance record that does not match the canonical terms, or any automated path to credentials or approval.
8. Session, transport, headers and redirects
In scope. TLS configuration, response headers, caching, CORS, and any redirect on the public site and the test environment.
Intended behaviour. HTTPS only with modern ciphers; HSTS; a restrictive Content-Security-Policy; X-Content-Type-Options: nosniff; a sane Referrer-Policy; Cache-Control: no-store on every authenticated or tokenised response; no wildcard CORS origin combined with credentials; no redirect that accepts an attacker-controlled destination.
How to test. Run a TLS configuration assessment against the host. Diff headers between public, tokenised and error responses. Attempt CORS preflight from a foreign origin with credentials and with unusual methods and headers. Check whether tokenised JSON is cached by an intermediary or the browser. Test every parameter that resembles a return path for open redirect, including protocol-relative, whitespace-prefixed and encoded targets. Attempt response splitting through reflected header values.
A finding is. A weak TLS configuration, a missing security header on a sensitive response, a cacheable tokenised response, credentialed wildcard CORS, an open redirect, or any header injection.
9. Rate limiting, abuse and resource exhaustion
In scope. Request-rate behaviour of the public and test endpoints. Volumetric denial-of-service against the hosting platform is explicitly out of scope — see §11.
Intended behaviour. Cheap endpoints tolerate load without failing open; expensive endpoints are bounded by input caps rather than unbounded work; failed authentication does not become a free oracle at scale.
How to test. Measure the point at which throttling engages on session issuance, the webhook, and the sandbox aggregate endpoint, using controlled, agreed request rates only. Attempt key and signature brute force within those limits. Submit maximum-size and maximum-nesting payloads to look for superlinear processing time. Test whether throttling can be evaded by varying X-Forwarded-For, source port, casing of the path, or trailing slashes.
A finding is. An endpoint whose cost grows superlinearly with input, a throttle that can be trivially evaded, an authentication endpoint with no rate control, or a control that fails open under load.
10. Dependencies, build artefacts and disclosure
In scope. The published dependency inventory and everything the build ships publicly.
Intended behaviour. A CycloneDX 1.5 SBOM is published and current; runtime dependencies carry no known exploitable vulnerabilities; no secret, internal hostname, or private endpoint appears in the shipped bundle, source maps, robots.txt, sitemap.xml, llms.txt, or any public document.
How to test. Reconcile the published SBOM against the assets actually served. Check each runtime dependency against public advisory data and judge real exploitability in this context rather than reporting raw advisory counts. Enumerate build artefacts and public files for leaked configuration, commented-out endpoints, admin paths, and test fixtures. Attempt to reach any administrative route discovered this way.
A finding is. A reachable and exploitable dependency vulnerability, an SBOM that materially misstates what ships, a leaked internal endpoint or credential, or an administrative route reachable without authorisation.
11. Out of scope
- Volumetric denial-of-service or stress testing against the hosting platform.
- Social engineering, phishing, or any contact with staff or suppliers outside the named contact.
- Physical security.
- The hosting provider's control plane and any infrastructure outside this application.
- Production tenants and real hotel data. This environment contains none; if you believe you have reached production data, stop and report it immediately as a critical finding.
12. Rules of engagement
- Test only the paths listed here, only within the agreed window, using the issued key.
- Do not exfiltrate data beyond what is needed to evidence a finding, and do not retain the synthetic dataset after the engagement closes.
- Report critical findings immediately to natromrich@googlemail.com; we acknowledge within one working day.
- No public disclosure before we have confirmed a fix, or 90 days from the report, whichever is sooner.
- The engagement key is revoked when the test closes.
13. Reporting format we can act on
For each finding: title, severity with justification, affected endpoint and method, the exact request that reproduces it, observed versus expected behaviour, realistic impact in the context of hotel invoicing data, and a suggested remediation. A machine-readable list of requests is welcome alongside the narrative. We fix confirmed critical and high findings before any pilot property is connected, and we will share the remediation evidence with you.
Apex Flow Technology Ltd is a private limited company registered in England & Wales. Company No. 17347345. Registered office: 29 Railway Road, Rhoose, Barry, Wales, CF62 3FE.