Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Security

- **The Census API key is no longer written to logs.** `geoinfo_from_params()` logged its request parameters at INFO after adding `key`, so the key appeared in notebook outputs (hundreds of times per call through the hierarchical geography lookup). It now logs before adding the key. Keys passed on to `morpc.req` are redacted there from morpc 0.7.5 (morpc/morpc-py#207).

## [0.6.4] — 2026-09-23

### Fixed
Expand Down
3 changes: 2 additions & 1 deletion morpc_census/geos.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,9 +486,10 @@ def geoinfo_from_params(param_dict: dict, year: int = 2024, output: Literal['lis
if 'in' in param_dict:
params.update({'in': param_dict['in']})

# Log before adding the API key, so the key never reaches logs or notebook outputs.
logger.info(f"Getting GEOIDS from {url} and params: {params}.")
if k := _get_api_key():
params['key'] = k
logger.info(f"Getting GEOIDS from {url} and params: {params}.")
json = morpc.req.get_json_safely(url, params=params)

if output == 'list':
Expand Down
13 changes: 13 additions & 0 deletions tests/test_geos_hierarchical.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,16 @@ def test_county_subdivision_parts_pair_each_county_with_its_own_subdivisions():
("county subdivision:18000", "county:049", "state:39"),
]
assert len(result) == 3


def test_geoinfo_from_params_does_not_log_the_api_key(caplog):
# The key is sent with the request but must never reach the logs, which end up in committed
# notebook outputs. See #7.
from morpc_census.geos import geoinfo_from_params
secret = "s3cr3t-api-key"
caplog.set_level("DEBUG")
with patch("morpc_census.geos._get_api_key", return_value=secret), \
patch("morpc.req.get_json_safely", return_value=[["GEO_ID", "NAME"], ["1600000US3918000", "Columbus"]]) as mock:
geoinfo_from_params({"for": "place:18000", "in": "state:39"})
assert mock.call_args.kwargs["params"]["key"] == secret
assert secret not in caplog.text
Loading