Skip to content

Commit 516ce5a

Browse files
committed
docs: add security policy with vulnerability reporting and tiered fix rules
1 parent 78b9273 commit 516ce5a

1 file changed

Lines changed: 143 additions & 0 deletions

File tree

SECURITY.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Security Policy
2+
3+
## Reporting a Vulnerability
4+
5+
**This is a public repository. Do not open a public GitHub issue for a security
6+
vulnerability** — that discloses it to everyone before a fix is available.
7+
8+
Instead, report privately through GitHub's **Private Vulnerability Reporting**:
9+
10+
1. Go to the **Security** tab of this repository.
11+
2. Click **Report a vulnerability**.
12+
3. Describe the problem, including steps to reproduce, affected version(s), and
13+
the impact.
14+
15+
We will acknowledge the report, keep you informed as we investigate, and
16+
coordinate the disclosure timeline and a fixed release with you. Please give us
17+
a reasonable window to ship a fix before any public disclosure.
18+
19+
## Scope
20+
21+
This repo is the **MarketData PHP SDK** — a client library published to
22+
Packagist as `marketdataapp/sdk-php` and installed into consumers' applications
23+
via Composer. It runs on the consumer's machine (or their servers), not on
24+
MarketData infrastructure. The security concerns that matter here are therefore
25+
about how the library treats *its consumers*:
26+
27+
- **Credential handling** — the caller's API token must never be logged
28+
verbatim, leaked in exception messages, or written to disk. The token is sent
29+
only in the `Authorization` header (never in query strings), request logging
30+
emits URLs without credentials, and the `headers()` utility partially redacts
31+
sensitive headers. Token resolution also reads the `MARKETDATA_TOKEN`
32+
environment variable and `.env` files (phpdotenv) — mishandling of those
33+
sources is in scope. Regressions in any of this are in scope.
34+
- **Transport security** — TLS is validated by default via Guzzle and the SDK
35+
exposes no skip-verify option. Anything that weakens this is in scope.
36+
- **Injection into outbound requests** — request-building that lets caller
37+
input smuggle headers, path segments, or query parameters it shouldn't.
38+
- **Response parsing safety** — the JSON decoding and typed-object hydration
39+
path (and the CSV/HTML format paths) handling hostile or malformed API
40+
responses without code execution, resource exhaustion, or crashes that a
41+
consumer can't defend against. Anything that routes response data into
42+
`unserialize()`, `eval()`, or dynamic includes is in scope by definition.
43+
- **Supply-chain integrity of the published package** — the release pipeline
44+
(`prepare-release.yml`, tag creation, and the Packagist sync from GitHub
45+
tags), and the dependency tree declared in `composer.json`.
46+
47+
Out of scope:
48+
49+
- **The MarketData API backend** itself. Report API/server vulnerabilities
50+
through the API's own channel, not here.
51+
- **Third-party dependencies.** Vulnerabilities in Composer-resolved
52+
dependencies (Guzzle, Carbon, phpdotenv, etc.) are tracked by Dependabot
53+
(see `.github/dependabot.yml`); report them upstream. We will bump the
54+
affected dependency here once a fixed version exists.
55+
56+
## Security Fix Policy
57+
58+
This policy governs how security fixes are applied to this repository, including
59+
fixes made by automated agents (e.g. Claude Code) working in the repo. It sorts
60+
every security fix into one of two tiers.
61+
62+
The dividing line for a **library** is *consumer compatibility*. A fix that any
63+
consumer can pick up with `composer update`, with no source or behavior change
64+
on their side, is low-risk. A fix that forces consumers to change their code or
65+
adapt to changed runtime behavior is a breaking change and follows SemVer —
66+
those get the maintainer gate.
67+
68+
### Tier 1 — Fix immediately (no approval needed)
69+
70+
Security fixes that are **API- and behavior-compatible for legitimate
71+
consumers**. Existing callers keep working the same way after upgrading; only
72+
the vulnerability is closed.
73+
74+
These may be fixed, tested, and committed right away. Every Tier 1 fix must be
75+
called out in its commit message, in `CHANGELOG.md`, and in the summary reported
76+
to the maintainer, so nothing ships silently.
77+
78+
Typical Tier 1 fixes:
79+
80+
- Tightening credential redaction, or plugging a token/secret/PII leak into
81+
logs or exception messages
82+
- Fixing injection in request building (header/path/query smuggling) where
83+
valid caller input is unaffected
84+
- Hardening the response-parsing path against malformed or hostile API
85+
responses (bounds, resource limits, null/missing-field handling)
86+
- Correcting a logic flaw in an existing security check without changing its
87+
public contract
88+
- Patching a vulnerable dependency by bumping to a compatible version within
89+
the existing `composer.json` constraints — no public API or behavior change
90+
for consumers
91+
- Hardening internal, non-public code paths (protected/private infra:
92+
transport, retry, logging internals) that consumers cannot observe or depend
93+
on
94+
- Fixing the release pipeline (CI workflows, tag/release automation)
95+
96+
### Tier 2 — Requires maintainer approval first
97+
98+
Any security fix that **breaks consumer compatibility or changes observable
99+
runtime behavior**. These must NOT be applied unilaterally. The agent or
100+
contributor stops, writes up the issue, the proposed fix, and the specific
101+
consumer impact, and waits for the maintainer's approval before proceeding.
102+
103+
A fix is **Tier 2** if it does any of the following:
104+
105+
- Removes, renames, or changes the signature of any **public** class, method,
106+
property, or parameter (a source-incompatible change — SemVer major)
107+
- Tightens input validation so that requests the SDK previously accepted are
108+
now rejected (could break existing callers)
109+
- Changes a user-visible default (timeouts, retry counts or backoff,
110+
rate-limit behavior, base URL, API version, token validation on client
111+
construction)
112+
- Changes an API/response contract — the shape of response objects, the
113+
exception types thrown (`ApiException`, `BadStatusCodeError`, `RequestError`,
114+
`UnauthorizedException`), or which exception a given failure raises — that
115+
consumers type-hint against or `catch` specifically
116+
- Raises the minimum PHP version (currently `^8.2`), tightens an existing
117+
dependency constraint in a way that can break consumers' dependency
118+
resolution, changes the package name, or otherwise forces a consumer to
119+
change their project to keep using the SDK
120+
- Adds a new required dependency to `composer.json`
121+
- Changes how the token is resolved (parameter → `MARKETDATA_TOKEN` env var →
122+
`.env` file) in a way that alters which credential an existing setup picks up
123+
124+
### Classification rules
125+
126+
- **When in doubt, it's Tier 2.** If it is unclear which tier a fix falls into,
127+
treat it as Tier 2 and ask for approval.
128+
- **No urgency exception.** Even for a critical, actively-exploitable
129+
vulnerability, a compatibility-breaking (Tier 2) fix waits for maintainer
130+
approval. Flag the urgency loudly, propose the fix, and wait. The maintainer
131+
is always the gate for changes that break consumers. (If a break is genuinely
132+
unavoidable to close a critical hole, that's a maintainer decision about
133+
cutting a major version — not an agent's.)
134+
135+
### Release of security fixes
136+
137+
Tiering governs *what* may be changed; the repo's normal release rules govern
138+
*what ships to consumers*. A Tier 1 fix may be committed to a branch and merged
139+
via the usual PR flow. **Publishing a release** — completing the release
140+
readiness gates in `release-readiness/`, cutting the `vX.Y.Z` tag, and letting
141+
Packagist pick it up — requires explicit maintainer confirmation, exactly like
142+
every other release (see `.github/RELEASE_PROCESS.md`). Automated agents never
143+
cut or publish a release on their own.

0 commit comments

Comments
 (0)