Vendor: Desveladisimo
This document describes the security posture of the BottleneckIQ Forge app for Atlassian Cloud Jira. It complements the public Privacy Policy with an engineering-level audit trail.
1. Architecture summary
- Forge Custom UI app. Frontend runs in an Atlassian-hosted iframe; backend runs on AWS App Runner (US East 1).
- Backend: FastAPI + SQLAlchemy 2 + Alembic on Python 3.13.
- Database: Amazon RDS Postgres, encrypted at rest (AWS KMS).
- Authentication: every backend request authenticated via Forge Invocation Token (RS256, signed by Atlassian).
- No direct outbound calls to Jira from the backend — all Jira reads go through the Forge runtime using the install-granted scopes (Connect-style
read:jira-work+read:jira-user, plus the granular OAuth 2.0 scopesread:issue:jira,read:issue.changelog:jira,read:issue-meta:jira,read:jql:jira,read:project:jira,read:user:jira).
2. Authentication
Inbound: Forge Invocation Tokens
Every request to the backend carries a Forge Invocation Token (FIT) attached by the Forge runtime. The backend's JWT auth middleware validates the FIT against Atlassian's JWKS:
- Signature verification against the cached JWKS bundle (refreshed at container build time).
audclaim must match the Forge app ID (set per environment from AWS SSM Parameter Store).iatwindow enforced at 3 minutes (Atlassian's documented FIT lifetime).expenforced;nbfenforced if present.
No header bypass
The middleware has no header-based bypass for "trusted internal" calls. Tests bypass via dependency injection, never via headers — keeping the production path single-purpose.
3. Tenant isolation (defense in depth)
Three layers, each independent:
Layer 1: App-level tenant_id filtering
Every query against a tenanted table includes WHERE tenant_id = ?. Service helpers accept a TenantContext and apply the filter; routers thread the context through. This is the primary guarantee.
Layer 2: Postgres Row-Level Security (RLS)
Every tenanted table has ROW LEVEL SECURITY (with FORCE) enabled. Per-table policy: USING (tenant_id = current_setting('app.current_tenant', true)). The current-tenant FastAPI dependency runs set_config('app.current_tenant', <client_key>, true) per request, after auth. The predicate fails closed when the GUC isn't set: an unauthenticated request, a forgotten WHERE, or a developer running raw SQL via the app role gets zero rows.
Layer 3: Cascading deletes on uninstall
avi:forge:uninstalled:app is forwarded to the backend, which deletes the tenants row. Foreign keys on every dependent table use ON DELETE CASCADE, so the entire tenant graph drops in one transaction.
4. Data classification & handling
What we collect
Pulled via Forge's read-only scopes for issues, projects, and users (Connect-style read:jira-work + read:jira-user for the dashboard's user-context calls; granular OAuth 2.0 scopes — read:issue:jira, read:issue.changelog:jira, read:issue-meta:jira, read:jql:jira, read:project:jira, read:user:jira — for the server-side webhook + reconcile handlers that run as the app):
| Field | Type | Sensitivity |
|---|---|---|
| Issue ID, key, summary | Identifying | Low (project-level data, not personal) |
| Status, type, priority, project_key | Metadata | Low |
| Assignee display name | Personal data | Personal — visible to the same Jira users that already see it in Jira |
| Created / updated / done / resolution timestamps | Metadata | Low |
| Story Points, Sprint custom fields | Metadata | Low |
| Issue changelog (status transitions only) | Audit data | Low |
| Sprint metadata (id, name, start, end, complete dates) | Metadata | Low |
Explicitly not collected: issue descriptions, comments, attachments, worklogs, time tracking entries, security-restricted issues, custom fields beyond Story Points / Sprint, or any field outside the list above.
Where it lives
- AWS US East 1 (Northern Virginia) — RDS Postgres, encrypted at rest with AWS KMS-managed keys.
- App Runner instances in the same region with TLS 1.2+ in transit.
- CloudWatch Logs, retention bounded per environment (30 days in prod; 14 days in dev).
Retention
- While installed: indefinitely.
- On uninstall: within minutes (Forge fires the lifecycle event; backend cascades the delete).
- In backups: RDS automated snapshots retain 7 days; data ages out of all backups within 7 days post-uninstall.
- Manual deletion request: customers can email support and request immediate manual purge from backups (best-effort).
5. Sub-processors
| Sub-processor | Purpose | Data handled |
|---|---|---|
| Amazon Web Services, Inc. | Hosting (App Runner, RDS, CloudWatch) | All. |
| Atlassian, Inc. | Forge runtime; FIT issuance; webhook delivery | The Forge install identifier + transient issue payloads in flight. |
| Anthropic, PBC | AI-generated bottleneck explanations (single English sentence per insight) | Numeric signals only. No issue keys, summaries, assignees, or any identifying content sent to Anthropic. Disable-able per tenant. |
Material changes to sub-processors are communicated via the Privacy Policy and an in-app notice.
6. Secrets management
| Secret | Storage | Access |
|---|---|---|
| RDS database credentials | AWS Secrets Manager (rotation enabled) | Read by App Runner instance role only. |
| Forge app ID | AWS SSM Parameter Store (public ID, scoped per env) | Read by App Runner runtime. |
| Anthropic API key | AWS Secrets Manager | Read by App Runner runtime when AI feature enabled. |
| Atlassian JWKS | Baked into container at build time; refreshed on deploy | Public key set; not a secret per se. Rotation handled by re-deploy on kid mismatch. |
We do not:
- Store any per-tenant Atlassian API tokens (Forge issues short-lived FITs instead).
- Store secrets in the codebase.
- Print secrets in logs.
7. Dependency hygiene
- Backend: Python deps managed by
uvagainstpyproject.toml.uv.lockchecked into the repo. - Frontend: Node deps with checked-in lockfiles.
- Infra: AWS CDK Python deps with checked-in lockfile.
- Vulnerability monitoring: GitHub Dependabot enabled on the repo. Alerts triaged within the published SLA.
- Container images: rebuilt on every deploy via the GitHub Actions workflow; base image is
python:3.13-slim. Atlassian-published JWKS refreshed on every rebuild.
8. Incident response
If a security issue is reported (via the security email or otherwise):
- Triage within 1 business day.
- Acknowledge to the reporter with the assessed severity.
- Patch with a timeline proportional to severity:
- Critical (data exposure, auth bypass) — same week.
- High (privilege escalation, dependency CVE with active exploitation) — within two weeks.
- Medium — next monthly release.
- Low — backlog.
- Notify affected customers via the support email + a release-notes entry. For confirmed customer-data exposure, notification within 72 hours per GDPR.
- Public disclosure coordinated with the reporter; CVE if applicable.
9. What is NOT in scope today
- SOC 2. Not certified. On the roadmap when a customer's procurement requires it.
- Penetration test report. Internal review of the codebase covers the OWASP top 10 conceptually but a formal external pen-test hasn't been commissioned yet.
- HIPAA, PCI, FedRAMP. Not in scope; the app does not process medical, payment, or US-government data by design.
- Multi-region failover. Single-region (us-east-1) by design.
10. Contact
- Security issues: security@bottleneckiq.com
- General support: support@bottleneckiq.com