Plinth.app
Automated Security Assessment — Web Application & TLS
1. Project overview
Plinth engaged CyberGrid to perform an Automated Security Assessment of its production web application surface. The engagement was scoped to the externally-accessible assets enumerated in §2, using the toolchain documented in §5. The assessment ran during the agreed maintenance window and was non-destructive.
The objective is to identify externally-visible security weaknesses that automated tooling can reliably detect — missing security controls, exposed information, known vulnerabilities in deployed software, and TLS misconfiguration — so that Plinth can address them prior to manual security testing or production exposure.
2. Scope
The following assets were in scope for this assessment:
- app.plinth.app — primary web application
- api.plinth.app — public REST API
- TLS endpoints on ports 443 across both hosts
The following are explicitly out of scope and require manual penetration testing to assess:
- Authenticated user flows (post-login behaviour)
- Multi-step business logic (payment flows, account transitions, workflow state)
- Authorization between user accounts (IDOR, horizontal/vertical privilege escalation)
- Race conditions, time-of-check/time-of-use flaws
- Social engineering, physical security, third-party integrations
3. Executive summary
Across the engagement, the assessment surfaced 7 findings distributed across severity classes. There were no Critical findings. The most significant issue identified was a publicly-accessible .git directory on api.plinth.app, classified as High severity, which exposes source code history.
The overall security posture of the externally-visible attack surface is moderate. The High finding should be remediated immediately. The Medium findings represent meaningful information leakage and should be addressed within the standard release cycle. Low and Informational findings are best-practice improvements that compound over time.
Top remediation priorities
- Remove or block public access to .git directory on the API host (Finding F-001)
- Disable GraphQL introspection in production (Finding F-002)
- Implement a Content-Security-Policy header on the primary application (Finding F-003)
4. Findings detail
Publicly accessible .git directory
The .git directory was found to be served by the web server on api.plinth.app. This permits a remote unauthenticated attacker to clone the application's source code history, including any commits that contained credentials, internal documentation, or non-public business logic.
# HTTP request GET /.git/HEAD HTTP/1.1 Host: api.plinth.app # Response HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 23 ref: refs/heads/main
- https://api.plinth.app/.git/HEAD (200 OK, 23 bytes)
- https://api.plinth.app/.git/config (200 OK, 305 bytes)
- https://api.plinth.app/.git/logs/HEAD (200 OK, 4.2 KB)
Add a block rule for /.git/ at the web server or reverse proxy level. For nginx:
location ~ /\.git { deny all; return 404; }
For long-term hygiene, ensure the deployment pipeline does not copy version control metadata into the published artifact (e.g. use .dockerignore or git archive).
- OWASP Web Security Testing Guide — Review Webserver Metafiles (WSTG-INFO-03)
- CWE-538: Insertion of Sensitive Information into Externally-Accessible File or Directory
nuclei (template: http/exposures/configs/git-config.yaml)
GraphQL introspection enabled in production
The GraphQL endpoint at /graphql responds to introspection queries, exposing the complete schema (types, queries, mutations, deprecated fields). While not a vulnerability in itself, this dramatically lowers the cost of reconnaissance for an attacker attempting to find authorization or input-validation flaws.
# Request POST /graphql HTTP/1.1 Host: api.plinth.app Content-Type: application/json {"query": "{ __schema { types { name } } }"} # Response (200 OK, 14.8 KB) { "data": { "__schema": { "types": [ { "name": "User" }, { "name": "Account" }, { "name": "InternalAdminMutation" }, ... 87 more type definitions exposed ] } } }
Disable introspection in production. For Apollo Server v4+:
const server = new ApolloServer({ typeDefs, resolvers, introspection: process.env.NODE_ENV !== 'production', });
Note: disabling introspection is security through obscurity, not strong security. Combine with field-level authorization, query depth limiting, and query complexity scoring for defense in depth.
nuclei (template: http/misconfiguration/graphql/graphql-detect.yaml)
Missing Content-Security-Policy header
The primary application response does not include a Content-Security-Policy header. CSP is the primary browser-side defense against cross-site scripting; without it, even a single unescaped output point in the application can become a full XSS vector.
# Response headers, GET https://app.plinth.app/ HTTP/1.1 200 OK Content-Type: text/html Strict-Transport-Security: max-age=31536000 X-Frame-Options: DENY X-Content-Type-Options: nosniff (no Content-Security-Policy header present)
Add a Content-Security-Policy header. Start with a report-only policy to gather data without breaking the app, then transition to enforcement. A reasonable starting point for a single-page application:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://js.stripe.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.plinth.app;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
report-uri /csp-report
nuclei (template: http/misconfiguration/http-missing-security-headers.yaml)
Deprecated TLS protocol versions supported
The TLS endpoint accepts connections using TLS 1.0 and TLS 1.1, both deprecated by the IETF (RFC 8996) and major browsers as of 2020. Modern clients negotiate TLS 1.2+ regardless, but supporting older versions exposes the surface to known downgrade attacks (BEAST, POODLE) and signals insufficient TLS hygiene to security-aware auditors.
$ testssl.sh --protocols app.plinth.app Testing protocols via sockets SSLv2 not offered (OK) SSLv3 not offered (OK) TLS 1 offered (deprecated) TLS 1.1 offered (deprecated) TLS 1.2 offered (OK) TLS 1.3 offered (OK)
Configure the TLS terminator to accept only TLS 1.2 and 1.3. For nginx:
ssl_protocols TLSv1.2 TLSv1.3;
testssl.sh
User enumeration via password reset
The password reset endpoint returns distinguishable responses for valid versus invalid email addresses, allowing an unauthenticated attacker to enumerate registered users. While this does not directly expose user data, it lowers the cost of credential stuffing and targeted phishing campaigns.
# Valid email POST /api/auth/reset {"email": "ceo@plinth.app"} → 200 OK · {"sent": true} # Invalid email POST /api/auth/reset {"email": "nonexistent@example.com"} → 404 Not Found · {"error": "user not found"}
Return an identical response (200 OK, generic success message) regardless of whether the email matches a registered user. Internally, log the lookup result for analytics, but do not leak it to the client.
Server version disclosure
HTTP responses include the Server: nginx/1.23.4 header, disclosing both the server software and minor version. This is useful for adversaries when correlating against known CVEs but is not exploitable on its own.
server_tokens off;
HSTS configured but not preloaded
The HSTS header is present with a one-year max-age, but the preload directive is not set, and the domain is not on the HSTS preload list. Preloading provides protection on first visit, before any HSTS header has been received.
Add includeSubDomains; preload to the HSTS header and submit the domain to hstspreload.org.
5. Methodology
This assessment used the following automated tooling. All tools are open-source, widely audited, and run in a sandboxed Docker container in our scanning infrastructure.
- nuclei v3.3.0 — template-based vulnerability scanner with ~8,000 community-maintained detection templates (ProjectDiscovery)
- httpx v1.6.9 — fast HTTP probing for asset discovery (ProjectDiscovery)
- testssl.sh — TLS configuration analysis
- nmap — network port and service detection
Findings are normalized to a common severity scale (see §6) derived from the OWASP Risk Rating Methodology. Each finding is reviewed automatically against a deduplication ruleset to suppress noisy or low-confidence detections. False positives are minimized; we err on the side of reporting fewer, higher-confidence findings rather than overwhelming the customer with informational noise.
6. Severity rubric
The severity assigned to each finding reflects the direct exposure if the finding were exploited in isolation, combined with the practical exploitability of the issue. Severity ratings are guidelines — they are not a substitute for your own threat-model-informed prioritization.
Critical
An issue with trivial exploit difficulty and substantial business impact. Examples: unauthenticated remote code execution, exposed credentials granting admin access, unauthenticated access to production customer data.
High
An issue that, if exploited, would result in unauthorized disclosure of sensitive information, partial loss of application control, or escalation toward Critical-level impact when combined with other findings.
Medium
An issue that does not directly expose application functionality or sensitive data but materially weakens the security posture. Often a missing control or a configuration that aids attacker reconnaissance.
Low
An issue with limited direct security impact but which contributes to overall risk in combination with other findings. Often best-practice deviations.
Informational
An observation with no direct security impact but worth noting for security hygiene or audit context.
7. Disclaimer
→ Important — please read
This document reports the results of an automated security assessment. It is not a penetration test. Automated tooling detects known vulnerability patterns, missing controls, and common misconfigurations. It does not identify business logic flaws, complex authorization issues, multi-step exploit chains, or vulnerabilities that require human reasoning about the application's intended behavior.
An absence of findings in any category does not indicate that no such vulnerabilities exist; it indicates that automated tooling did not detect them within the engagement scope and time window. For comprehensive security validation, manual penetration testing by qualified human testers is recommended in addition to — not in place of — automated assessment. CyberGrid offers Penetration Testing as a separate engagement-based service for that class of finding.
CyberGrid makes no warranty as to the usefulness, accuracy, or completeness of this report. The customer is solely responsible for the secure operation of all systems it owns or maintains. This report is confidential between CyberGrid and the named customer and is not intended for use as evidence in any audit, regulatory filing, or legal proceeding unless explicitly accepted by the receiving party.