Conversation
validateProjectConfigSecurity filters on MCPServerConfig.source, but loadAppConfig stripped the loader wrapper (which carries provenance) before validation, so project-level hardcoded-credential checks never ran in the shipped app. Copy wrapper.source onto the runtime server objects and add a regression test that models the real unwrap path. Fixes Nano-Collective#1248 Signed-off-by: halaxy <63827956+89799969@users.noreply.github.com>
nc-review: comments — 2 nits@89799969 — a few things worth a look, none blocking. The change correctly fixes the integration break described in issue #1248 by copying ⚪ nit · The new regression test demonstrates that the unwrap preserves ⚪ nit · Issue #1248 also flags 🔴 blocking · 🟠 a reviewer would ask for a change · ⚪ optional Automated code review — correctness, security, design, tests, plus duplicates and scope. A human still decides; this is not a substitute for review and is not exhaustive. The required status checks separately cover lint, formatting, types, unused dependencies, the test suite and the build. This bot never merges. Maintainers can rerun with |
will-lamerton
left a comment
There was a problem hiding this comment.
Thanks for this - the root cause analysis is right, the fix in source/config/index.ts is the correct minimal change, and both consumers (useAppInitialization.tsx:210, plain/initialize.ts:163) are covered by it. Changeset is correct too.
Two things I'd like sorted before merge.
1. Re-enabling the scanner emits false positives for correct usage
loadProjectMCPConfig runs substituteEnvVars(mcpServers) at source/config/mcp-config-loader.ts:81, before mapServerConfig. So by the time validateMCPConfigSecurity sees a server, "$API_KEY" has already been resolved to its literal value, and the !value.startsWith('$') check at source/config/validation.ts:17 is always true.
I ran the real path with a temp project .mcp.json:
--- loaded env values after substitution ---
good-citizen: source=project API_KEY="sk-live-secret-from-env" # was "$MY_REAL_KEY"
unset-var: source=project API_KEY="" # was "$NOT_SET_ANYWHERE"
actually-bad: source=project API_KEY="sk-hardcoded-literal"
--- security warnings captured ---
* good-citizen
* unset-var
* actually-bad
total: 3
Only actually-bad is a genuine finding. The other two are users doing the right thing, and the warning tells them to "Consider using environment variable references (e.g. $API_KEY)", which is exactly what they wrote. Every project with an env key matching token/key/secret/password/auth would get a security warning on startup.
This is likely why the dead filter went unnoticed: the check has never run against real data. Fix is to validate the pre-substitution values, either by scanning inside loadProjectMCPConfig before substituteEnvVars, or by carrying the raw env/headers through for the validator.
2. The regression test can't catch the regression
The new test builds mcpServers by reimplementing the unwrap inline; it never calls anything from source/config/index.ts. I checked out this branch, reverted source/config/index.ts to main, and re-ran:
✔ loader unwrap keeps source so project configs reach the validator
5 tests passed
Green with the production fix entirely absent. source/config/index.spec.ts already has the harness for a real one (temp dir + reloadAppConfig): write a .mcp.json into a temp cwd, call reloadAppConfig(), assert config.mcpServers[0].source === 'project'. That also covers nc-review's first nit, since asserting the warning actually fires is what surfaces issue 1 above.
nc-review's second nit (mapServerConfig still drops .source) is fair but not blocking - effective-config.ts:801 is the only other caller and it destructures source off the wrapper itself.
Also note the required pr-checks haven't run on this commit yet, so lint/types/tests/build are still unverified here. I'll get that approved.
…load path Re-enabling validateProjectConfigSecurity exposed two follow-ups from review: env substitution ran before the scanner so \ looked hardcoded, and the regression test never called loadAppConfig. Snapshot rawEnv/rawHeaders at map time, prefer them in the scanner, and drive a reloadAppConfig test through a temp .mcp.json so reverts of the unwrap or the raw snapshot both fail. Addresses review on Nano-Collective#1256 Fixes Nano-Collective#1248
|
Pushed 1. False positives from env substitution 2. Regression test that can fail
Also exported Verified: validation + loader specs pass; |
Regenerated agents.config.schema.json after adding pre-substitution credential snapshots used by the MCP scanner. Fixes Config Schema Freshness on Nano-Collective#1256
Adds loader tests for NANOCODER_PROVIDERS_FILE, invalid provider JSON, and env-sourced rawEnv; validation edge cases for empty/non-string values. Restores cwd before temp-dir rm so Windows afterEach cleanup no longer EPERMs. Aims to recover coverage lost by the scanner change.
|
CI note on
I cannot re-run the workflow without admin rights. Please re-run |
Problem
validateProjectConfigSecurityis the app's defense-in-depth check for hardcoded credentials in project-level MCP configs. It filters onMCPServerConfig.source === 'project', butloadAppConfigunwrappedMCPServerWithSourceto plainMCPServerConfigbefore validation:The loader only tracks provenance on the wrapper, so every runtime object had
source: undefined, the filter was always empty, and the scanner never ran. The unit test passed only because it hand-set.sourceon the inner type — a shape that never occurs in the real data flow.Fix
When unwrapping, copy the wrapper's
sourceonto the runtime object (MCPServerConfig.sourceis already an optional field):Both
useAppInitializationandplain/initialize.tsthen see real provenance.Tests
source/config/validation.spec.tssuite still passesloader unwrap keeps source so project configs reach the validatormodels the realMCPServerWithSource[]shape and asserts:project-serverin the project filtervalidateProjectConfigSecurityaccepts the preserved-source arraytsc --noEmitpasses.Fixes #1248