diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1bda3c4..6460173 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,6 +26,14 @@ the conversation toward config before anyone writes code. If a vendor is nearly compatible and something small blocks it, that is a bug in the adapter worth fixing, not a reason for a fourth one. +**A new adapter config must not commit the vendor's published rates.** Name the +tariff page and leave the figures to the operator's own `--pricing` table. Some +vendors' terms make their pricing confidential and override the usual +public-knowledge exclusion, so copying a rate out of a public page into a file +this project publishes is a disclosure by this project. Secret scanning will not +catch it, because a price is not a credential format; see SECURITY.md and the +test named there. + ## The rule about figures **No figure may be rendered without its n and its null.** diff --git a/SECURITY.md b/SECURITY.md index a55e577..3f7ddd9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -22,6 +22,40 @@ Please do not open a public issue for a security problem. it does not change the answer. - `.env` is gitignored. `.env.example` is the template and holds no values. +## What the automated controls cover, and what they do not + +This repository has secret scanning and push protection enabled. Both work by +recognising **credential formats**: a token that looks like a token gets caught, +and push protection rejects the commit before it lands. That is the right +control for a leaked key and it is the reason a key has never reached a commit +here. + +It is worth being precise about what that leaves. + +The closest thing to a disclosure this project has actually had was not a +credential. It was a **vendor's price**, quoted from their documentation into a +shipped config file, under a customer agreement that makes pricing information +confidential and expressly overrides the usual public-knowledge exclusion. No +scanner recognises a price. `0.042` is a float. Push protection would have +passed it through without a murmur, and did, until it was caught by reading. + +Contractual confidentiality is not a pattern-matching problem, so it does not +get a pattern-matching control. What guards it here is design and a test: + +- **Design.** plumbline ships no pricing figures for a vendor whose terms treat + them as confidential. The shipped entry names the tariff page and prices + nothing, and the operator supplies the numbers themselves through + `--pricing`, in their own working copy. Reading a published page and writing + the number down is the operator's act, not this project's. +- **A test.** `tests/test_cost_and_latency.py::test_no_shipped_entry_states_a_figure_for_the_confidential_vendor` + asserts that no shipped entry for that vendor carries a numeric price, in + either a price field or its source string. It fails if a number is put back + in a price field, and it fails if a price is quoted in the prose around one. + Both paths were verified by injecting a figure and watching it go red. + +If you are reviewing a change that touches pricing, that test is the control. +Do not assume a green secret-scanning badge says anything about it. + ## Run artifacts contain your data This is the part worth pausing on. diff --git a/docs/PLAN.md b/docs/PLAN.md index ab496ac..718f965 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -109,6 +109,21 @@ Settled during the build. Reopen one only with a reason, not from scratch. about the system under test. A decline is recorded as a refusal with its category and counted. The tradeoff is stated at the call site in `adapters/generative.py`; it is not an oversight. +- CodeQL default setup is kept for its **`actions`** coverage, not its Python + coverage. Workflow script injection is a real class of bug and `ci.yml` is + where it would hide. The Python queries are expected to be low yield on this + codebase: it is a CLI with no attacker in its threat model, run by an + operator on their own data with their own key. The first full scan produced + exactly one alert, a false positive on a test assertion + (`py/incomplete-url-substring-sanitization`, a substring check that makes no + security decision), dismissed with that reasoning. **Turn it off if a second + false positive appears on ordinary work**; at that point it is costing review + attention it is not repaying. It is a setting, not a workflow file, so + disabling it is one API call and leaves no trace in the tree. +- Secret scanning and push protection guard credential formats and nothing + else. The disclosure risk this project actually has is contractual, and the + control for it is the `--pricing` design plus a test. SECURITY.md says so in + full, because a green scanning badge invites the wrong assumption. ## Live validation