Repository navigation
Stop trusting the compromised eMudhra CA certificates - #198
Merged
Merged
Conversation
The hosted MCP server's image still carries certifi 2026.7.22 (cached pip_install layer built 2026-08-12) and Debian's unfixed ca-certificates, both of which trust "emSign ECC Root CA - G3". Append two layers at the end of the image chain: refresh ca-certificates from Debian and require certifi>=2026.10.19. Appending keeps every earlier layer cached, which matters because rebuilding the pip_install layer fails today (policyengine-us==1.764.6 is no longer on PyPI). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
2 tasks
policyengine-us 1.764.6 is no longer on PyPI, so any rebuild of the Modal image's pip_install layer fails, and the nightly contract job has failed on every run since its uv install could not resolve that pin. No policyengine 4.x or 5.x release can resolve either: every one with a [models] extra pins a 1.x policyengine-us that PyPI no longer hosts. Pin policyengine[models]>=6.10,<7 and policyengine-us==2.2.1, in both integration/pyproject.toml and the image definition. 2.2.1 is the newest policyengine-us that the newest policyengine (6.10.0) can import: its bundled manifest certifies the US data release for 2.2.1, and the release accepts only >=2.0.0,<2.3. With policyengine-us 2.59.0 (the latest on PyPI), `import policyengine` raises "Data release manifest is not certified for the runtime model version 2.59.0", which would break every UK and US tool. The resolved set is policyengine 6.10.0, policyengine-core 3.33.3, policyengine-uk 2.132.6, policyengine-us 2.2.1 and spm-calculator 1.0.0. Changing the pip_install list rebuilds that layer and every layer after it; the ca-certificates and certifi steps stay at the end of the chain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces the move to policyengine 6.10 and policyengine-us 2.2.1 in the previous commit, which changes results and breaks the US household tools. policyengine 6.10 cannot import the latest policyengine-us (2.59.0): its manifest certifies the US data for 2.2.1 only, so `import policyengine` raises. With 2.2.1, policyengine-us 2.x requires a county FIPS code or an explicit SPM geography for the default US household outputs, so every calculate_household and household_reform_impact call for the US raises SPM_GEOGRAPHY_REQUIRED; with a geography selected, outputs still change (for one New York single parent on $30,000, household_benefits falls from 26,172.92 to 4,919.13). Keep the deployed versions instead. policyengine-us has no release tags, so install 1.764.6 from the commit that set that version (92e6052d3e057c4e03b955d01a7ee4e3c2fb3131). A wheel built from that archive has the same 16,080 package files, byte for byte, as the PyPI wheel the policyengine 4.22.3 manifest records (sha256 2b2c5c02...). Also pin spm-calculator 0.3.1 and microdf-python 1.3.0, the versions the deployed image holds: policyengine-us 1.764.6 declares spm-calculator>=0.2.0 and policyengine 4.x microdf_python>=1.2.1, so a rebuild would otherwise take spm-calculator 1.0 and microdf-python 1.5. Both pip and uv resolve this to policyengine 4.22.3, policyengine-core 3.30.0, policyengine-uk 2.89.2 and policyengine-us 1.764.6, on Python 3.12 and 3.13. The rebuilt pip_install layer pulls certifi 2026.10.19. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This reverts commit 71ad069. The image and the [models] extra should not install policyengine-us 1.764.6 from a GitHub archive; they move to the current policyengine release instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move the Modal image and the [models] extra from policyengine>=6.10,<7 to policyengine[models]==6.11.1, the newest release, and list the four packages its [models] extra pins: policyengine-us 2.2.1, policyengine-uk 2.151.0 and policyengine-core 3.33.3 (spm-calculator 1.0.0 comes with the extra). policyengine's bundled manifest certifies the US and UK microdata for exactly these versions, so pinning policyengine alone and letting a later release change the country models silently is what the explicit pins prevent. Resolved with uv on Python 3.12 (the image's version): policyengine 6.11.1, policyengine-core 3.33.3, policyengine-uk 2.151.0, policyengine-us 2.2.1, spm-calculator 1.0.0, microdf-python 1.3.10. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
policyengine-us 2.x computes the Supplemental Poverty Measure in every
default US household output, and a state alone does not identify an SPM
area, so every calculate_household and household_reform_impact call for
the US raised SPM_GEOGRAPHY_REQUIRED.
Follow how policyengine.py, the PolicyEngine API and the web app supply
it: the household's five-digit county_fips, or an explicit spm selection
({"geography_kind": "national"} or a "metro" SPM area), with no default.
calculate_household, household_reform_impact and the household CLI
commands take an spm argument that is passed to policyengine.py's
calculate_household unchanged. Without either, the adapter rejects the
call before importing PolicyEngine and names the choices.
policyengine-us takes the state and the county independently, so a
county with no state (or in another state) would run a California
household in that county; the adapter checks the county against the
state. The state may now be given as state_code, the key policyengine.py
documents, as well as state_code_str, since policyengine-us 2.x honours
both. US results report the SPM selection and area used (`spm`); the
full policyengine.py receipt is several hundred kilobytes per household.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The US household commands on /connect and /forecasts/us, and the US example prompt on /connect, had no state or county, so they ran a California household by default and, with policyengine-us 2.x, are rejected for lacking an SPM geography. Give them the state and county (Los Angeles County, 06037) explicitly, as policyengine.py's own examples do, and document the county_fips and --spm inputs in the integration README. /forecasts/us is regenerated by economy/topics.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With policyengine 6.11.1 the /pe/code example (state_code "CA" only) raises SPM_GEOGRAPHY_REQUIRED. Add Los Angeles County's FIPS code, as policyengine.py's own documentation does, and say that a US household needs a county or an explicit national SPM selection. The edited snippet runs on policyengine 6.11.1 (income tax $5,020, net income $48,009 under the shown reform). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With no dataset argument the US population tools run policyengine's certified default, which in 6.11.1 is Populace US 2024 (populace_us_2024, built on CPS ASEC records). Say so in the adapter comment and the population_reform_impact description, which still said "CPS-based" only. The UK default (enhanced_frs_2023_24) is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Name the package as policyengine.py rather than by a short nickname, and say that all four pins change together. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #199
Incident
On 2026-10-20 it was disclosed that eight eMudhra (emSign) intermediate CA certificates were issued with private keys anyone can recover from the public certificate, so anyone can issue a certificate for any domain that chains to "emSign ECC Root CA - G3" (and, through cross-signatures, to Sectigo's "AAA Certificate Services"). Browsers and current operating systems already block them; what stays exposed is software that carries its own CA list or reads an operating-system CA store that has not been updated. Details: https://emudhra.we-have-a-problem.com
What in this repository trusted the affected roots
The hosted MCP server (Modal app
policyengine-macro-mcp, built fromintegration/modal_app.py) makes outbound HTTPS requests from its containers: Hugging Face microdata downloads forpopulation_reform_impact, and PolicyEngine data fetches. Its image carries two CA lists, and both trusted the compromised root:pip_installlayer was last built on 2026-08-12 (deploy run 31608860051:Successfully installed ... certifi-2026.7.22 ... hf-xet-1.6.0 ... requests-2.34.2). Every deploy since has reused that cached layer: the 2026-10-08 deploy (run 37774203311) reportsRequirement already satisfied: certifi in ./usr/local/lib/python3.12/site-packages ... (2026.7.22), and the 2026-10-12 deploy built no layers at all. The fixed release is certifi 2026.10.19.ca-certificates(used by urllib, aiohttp, and hf-xet 1.6.0, which reads the operating-system store). Modal'sdebian_slimbase is bookworm (the apt output in the same deploy logs showsbookworm,bookworm-updates,bookworm-security), and no step in the image refreshesca-certificates. The layers above it were built on 2026-08-12, before Debian published the fixed20250419~deb12u2tobookworm-security.No
grpciois installed in the image (not in the 2026-08-12 install list), and no code in the repository overrides TLS verification or sets a custom CA bundle.Not affected:
macromodserves this repository as a static site (noapi/directory, nopackage.json; the latest production deployment has a single static build and no functions), so there is no server-side code on Vercel.astral-sh/setup-uv@v8.3.2has noversioninput, so it installs the latest uv.Changes
1. CA lists in the image
integration/modal_app.pyappends two layers at the end of the image chain, after the editable installs:.apt_install("ca-certificates"), which Modal runs asapt-get updatethenapt-get install -y ca-certificates, upgrading the package to20250419~deb12u2frombookworm-security..pip_install("certifi>=2026.10.19"), which keeps the fixed certifi minimum whatever the layers above resolve to (certifi has no dependencies).2. Move to policyengine 6.11.1
The image's
pip_installlayer pinnedpolicyengine-us==1.764.6, which PyPI no longer hosts, so the layer could not be rebuilt, and the nightly contract job has failed on every run since because itsuv pip install -e ".[models]"cannot resolve that pin. Every policyengine release from 4.17.3 to 5.3.1 pins a policyengine-us 1.x version that PyPI no longer hosts. The only 4.x releases whose pins PyPI still hosts are 4.13.1 to 4.17.2, and those would move the models back to older versions than the server runs now.The image and the
[models]extra inintegration/pyproject.tomlnow pinpolicyengine[models]==6.11.1, the newest release, and list the country packages that release pins: policyengine-us 2.2.1, policyengine-uk 2.151.0 and policyengine-core 3.33.3 (spm-calculator 1.0.0 comes with the extra). policyengine's bundled manifest certifies its US and UK microdata for exactly these versions, andimport policyenginefails certification with any other policyengine-us, so a newer policyengine-us (PyPI has 2.75.0) cannot be used. There is no lockfile inintegration/.3. US households need a Supplemental Poverty Measure geography
policyengine-us 2.x computes the Supplemental Poverty Measure (SPM) in every default US household output, and a state alone does not identify an SPM area, so every US
calculate_householdandhousehold_reform_impactcall raisedSPM_GEOGRAPHY_REQUIRED.This follows how the other PolicyEngine projects supply it through policyengine.py 6.x: the household's five-digit
county_fips, or an explicitspmselection, with no default.household={"state_code": ..., "county_fips": ...}orspm={"geography_kind": "national"}. They say national measurement is a conscious analytical choice.spmbeside the household and does not infer national geography when it is omitted.SPMGeographyChoice.tsx) requires the user to choose national thresholds or a county FIPS code before it calculates.What changes in this repository:
calculate_household,household_reform_impact,core.pe_household,core.pe_household_impactand thepe-macro household/household-impactcommands take an optionalspmdict (--spmforpe-macro), passed unchanged to policyengine.py'scalculate_household(spm=...).county_fipsnor anationalormetroselection is rejected before PolicyEngine is imported. The error names the three choices.county_fips="36061"alone gavestate_code=CA). The adapter therefore requires the state with a county, and checks that the county's first two digits match the state's FIPS code.state_code, the key policyengine.py documents, or asstate_code_str, the key this package documented before. policyengine-us 2.2.1 reads both; this was checked for CA and TX. The adapter used to rejectstate_code.spmentry: the resolved selection and the SPM area each year was measured in. policyengine.py's full receipt is about 690 KB for one household, so it is not returned./connectand/forecasts/us(the latter regenerated witheconomy/topics.py), the/connectexample prompt, and the policyengine.py snippet on/pe/code. They now name Los Angeles County (06037), as policyengine.py's own examples do.The UK population default dataset stays
enhanced_frs_2023_24, which is in policyengine 6.11.1's manifest. policyengine's certified UK default is nowmicrocosm_uk_2024_25(policyengine/populace-uk-private). Switching to it would change the published UK costing, and/pe/validationrecords that a newer UK data release is deliberately not adopted, so this PR does not switch. With no dataset argument, the US population tools follow policyengine's certified default, as before. In 6.11.1 that default is Populace US 2024 (populace_us_2024).Which tool outputs change
All figures are from real runs. "Before" means policyengine 4.22.3, policyengine-us 1.764.6 and policyengine-uk 2.89.2, which is what the deployed server reports in its provenance. "After" means policyengine 6.11.1 with this branch.
calculate_householdUK: age 35, £50,000, 2026household_reform_impactUK: same person, basic rate 25%list_reform_parameterscalculate_householdUS: NY head of household aged 30, $30,000 wages, child aged 5household_benefits$17,910.02 (SNAP 287.68 + school meals 1,130.96 + Head Start 16,491.37); net income $51,334.70spmnational: benefits $1,446.08, net income $34,870.77calculate_householdUS: single, 35, $60,000, CA (county 06037 after)calculate_householdUS: single, 35, $50,000, CA (county 06037 after)population_reform_impactUS: CTC adult-dependent amount $1,000, 2026start_jobhit its 1,800 s job limit, and a local run with the old versions ended without outputpopulace_us_2024, 57,240 households; 2,903 s on a laptop)population_reform_impactUK: basic rate 21%, 2026enhanced_frs_2023_24, 53,508 households)Why the New York household's benefits fall. Geography does not cause it. In policyengine-us 2.x,
household_benefitscounts Head Start and Early Head Start only whengov.simulation.include_head_start_benefits_in_net_incomeis true. That parameter defaults to false. The child, aged 5, still qualifies for Head Start (New York's per-enrollee value is $16,491.37 in both versions), but the value is no longer counted. With that parameter set to true, policyengine-us 2.2.1 gives benefits of $17,937.45 for Albany County. The remaining $27.43 against the old $17,910.02 is SNAP (+$16.16) and school meals (+$11.28).The county matters too. Without a county, both versions apply the state's first county, Albany. New York County raises SNAP by $462 and lowers state income tax by $41.83 compared with Albany. Geography accounts for nothing else in this example. The earlier figures in this PR (26,172.92 falling to 4,919.13) came from a household whose inputs were not recorded, and the same Head Start rule is the likely cause there.
Committed outputs
Rechecked and unchanged. The UK household figures on
/pe/code,/models, the registry text incapabilities.pyand the pe-microsim paper (£7,486, £2,994, £39,520, +£1,872) match the runs above, so they are left as they are.Left alone because I could not rerun them. These all come from UK population runs:
/obr,/obr/validation,/obr/methodology,/models,/pe,/pe/validation,validation/figures/chart_data.json, and the pe-microsim and obr-macro papers and their PDFs;/svar.I have no Hugging Face token that can read the UK microdata, so publishing a new figure would break the rule that every number comes from a model run.
test_population.py::test_population_impact_uk_real_basic_ratecompares the costing with £6.46bn to within ±5%. Run it with a working token, and if it fails, regenerate those pages.Fixed. US commands and examples, listed under "Changes" above. None of them publishes a US figure.
Tests. The US household tests in
integration/tests/test_core.pynow pass a county or a national selection. New fast tests cover the geography rules.Checks
Image. Ephemeral
uvx modal runof a no-op function on the image fromintegration/modal_app.py, withPOLICYENGINE_MACRO_IMAGE_SOURCE=github, in workspacepolicyengine. Nothing was deployed. The rebuiltpip_installlayer took 118 s and installed policyengine 6.11.1, policyengine-core 3.33.3, policyengine-uk 2.151.0, policyengine-us 2.2.1, spm-calculator 1.0.0 and certifi 2026.10.23 on Python 3.12.10. The apt step installedca-certificates 20250419~deb12u2.Tools inside the image.
calculate_householdfor the New York example (county 36061) and for the UK example gave the "After" figures in the table above.population_reform_impactwithrun_tool_job's resources (4 CPUs, 8 GiB) did not finish within the 1,800 s job limit, and no memory error was reported. That run included the first 827 MB data download. On the deployed server the same call throughstart_jobalso hit the 1,800 s limit, so this is not new.population_reform_impactfailed for bothenhanced_frs_2023_24andmicrocosm_uk_2024_25: "Hugging Face rejected the configured credentials".CA trust, checked inside the built image.
The same before-and-after result was reproduced earlier in Docker (
python:3.12-slim-bookworm), starting from the unfixed state (ca-certificates 20230311+deb12u1, certifi 2026.7.22). Before the fix, requests + certifi gave HTTP 200 for the emSign chain, and urllib + the OS store gave HTTP 200 for both eMudhra hosts.Resolution.
uv pip compile integration/pyproject.toml --extra modelsresolves for Linux on Python 3.11, 3.12 and 3.13, all with the versions above. Python 3.10 did not resolve before this change either, because boe-var needs 3.11 or later.Nightly contract job. Dispatched on this branch: the
uv pip installresolved and the run gave 19 passed, 3 skipped. The three FRB/US tests skip because they need an editable FRB/US checkout, which this job's non-editable install does not provide. The same job also passed locally on Python 3.13.Integration tests, run as
test-integration.ymldoes (base install plus the OBR and SVAR packages, Python 3.13,uv run): tool surface 32 passed, reform input 28 passed, full fast suite 304 passed and 77 skipped. With--runslowon Python 3.12, all 13 household tests intest_core.pypass against the real models.Site.
site_contract.pyandsite_nav.pypass, as do all 14 generator--checkcommands fromsite-provenance.yml,forecast-archive.ymlanddata-immutability.yml.python3 -m pytest testsgives 1564 passed and 19 skipped, andforecasts/testspasses.This repository does not use towncrier:
CHANGELOG.mdis written at release time, so this PR has no changelog fragment.Operator actions after merge
macromod-hf(workspacepolicyengine). Its key isHUGGING_FACE_TOKEN(checked inside a container;HF_TOKENis not set). Hugging Face rejects its value:whoami-v2returns HTTP 401, and so dopolicyengine/policyengine-uk-data-privateandpolicyengine/populace-uk-private.policyengine/policyengine-uk-data-private, which holdsenhanced_frs_2023_24, the UK default. If the UK default later moves to policyengine's certifiedmicrocosm_uk_2024_25, the token also needspolicyengine/populace-uk-private.policyengine-macro-pe-dataholds per-year files written by policyengine 4.x. policyengine 6.11.1 rebuilds per-year files that do not record their data year, and the volume does not hold the source file, so every UK population tool (population_reform_impact,score_reformwith the UK microsimulation,svar_inflation_incidence,define_scenario_incidence, the OBR reform scoring) will need a download. With the current token that download fails.mainchangesintegration/**, sodeploy-mcp.ymlrunsmodal deploy integration/modal_app.py. In that run's Deploy step, confirm:pip_installlayer rebuilds withpolicyengine-6.11.1,policyengine-us 2.2.1andpolicyengine-uk 2.151.0;ca-certificatesreports20250419~deb12u2;test_remote_mcp.py::test_obr_shock_cggfailed withRedirect to https://policyengine--policyengine-macro-mcp-serve.modal.run/mcp not followed. That failure is not caused by this change, so judge the deploy by the Deploy step. After the token is replaced, runpytest integration/tests/test_population.py -k uk_real --runslowwith that token to recheck the £6.46bn costing.calculate_householdandhousehold_reform_impactmust now sendcounty_fipswith the state, orspm={"geography_kind": "national"}. US results change as shown above.Environment-variable verification
This PR adds no environment variables. Every variable the image, its functions and the workflows that build or test it read:
MODAL_TOKEN_ID,MODAL_TOKEN_SECRETdeploy-mcp.ymlDeploy stepenv:fromsecrets.*POLICYENGINE_MACRO_IMAGE_SOURCEgithubindeploy-mcp.ymlenv:; read bymodal_app.pywhen the image is definedHF_HOME,POLICYENGINE_MACRO_PE_DATA_DIRmodal_app.pyimage.env({...})onserve,run_tool_jobandcheck_overlay_gate; read by huggingface_hub andcore._pe_pop_data_folderPOLICYENGINE_MACRO_SOURCE_REVISION_*modal_app.py.env(SOURCE_REVISION_ENV)image layer; read bycore._provenanceHUGGING_FACE_TOKENmacromod-hfin workspacepolicyenginesecrets=[modal.Secret.from_name("macromod-hf")]on all three functions. Read by policyengine 6.11.1 (provenance/manifest.py, which also acceptsHF_TOKEN), by policyengine-core, and bycore._pe_pop_dataset's error hintHF_TOKENmacromod-hfserveandrun_tool_jobcopy it toHUGGING_FACE_TOKENwhen only it is setpolicyengine/populace-us)POLICYENGINE_MACRO_REMOTE_TESTS"1"indeploy-mcp.yml(smoke-test step) andvalidate-deployment.ymlenv:; read bytest_remote_mcp.pyPOLICYENGINE_MACRO_REQUIRE_PE"1"incontract.ymlenv:; read bytest_contract.py🤖 Generated with Claude Code