Repository navigation
Conversation
|
Thanks for opening your first pull request in the Codna CLI! Please make sure the checks are green and that the change follows CONTRIBUTING.md. A maintainer will review it shortly. |
| try: | ||
| sys.exit(main()) | ||
| except OverlayError as error: | ||
| print(json.dumps({"error": error.code, "message": error.message, **error.details}), file=sys.stderr) |
There was a problem hiding this comment.
🟡 MEDIUM · correctness — OverlayError 'error' detail key silently overwrites error code in main outpu
Line 111 raises OverlayError("manifest_invalid", ..., error=str(exc)), storing error=str(exc) in self.details. At line 260 the dict literal {"error": error.code, "message": error.message, **error.details} is constructed: when error.details contains the key "error", Python dict unpacking silently overwrites the earlier "error": error.code entry with the detail value (the JSON decode error string). The CLI stderr output for a corrupt manifest would show the JSONDecodeError message as the "error" field instead of "manifest_invalid", making the error code unrecognisable by any consumer parsing that JSON.
| print(json.dumps({"error": error.code, "message": error.message, **error.details}), file=sys.stderr) | |
| Rename the detail kwarg at line 111 to avoid collision: | |
| ```python | |
| raise OverlayError("manifest_invalid", f"{path} is not valid JSON", path=str(path), parse_error=str(exc)) from exc |
Or build the JSON dict defensively:
payload = {"error": error.code, "message": error.message}
payload.update(error.details)
print(json.dumps(payload), file=sys.stderr)
_Reply `@codna fix` and Codna opens a verified, test-green, risk-gated fix PR._
<!-- codna:review:fp=e0b38b7367cb8b4f -->
<!-- codna:finding eyJmcCI6ICJlMGIzOGI3MzY3Y2I4YjRmIiwgInBhdGgiOiAibWVtZW5naW5lL19jb2RuYV9vdmVybGF5LnB5IiwgImxpbmUiOiAyNjAsICJlbmRfbGluZSI6IG51bGwsICJzZXZlcml0eSI6ICJtZWRpdW0iLCAiY2F0ZWdvcnkiOiAiY29ycmVjdG5lc3MiLCAidGl0bGUiOiAiT3ZlcmxheUVycm9yICdlcnJvcicgZGV0YWlsIGtleSBzaWxlbnRseSBvdmVyd3JpdGVzIGVycm9yIGNvZGUgaW4gX19tYWluX18gb3V0cHUiLCAiZXhwbGFuYXRpb24iOiAiTGluZSAxMTEgcmFpc2VzIGBPdmVybGF5RXJyb3IoXCJtYW5pZmVzdF9pbnZhbGlkXCIsIC4uLiwgZXJyb3I9c3RyKGV4YykpYCwgc3RvcmluZyBgZXJyb3I9c3RyKGV4YylgIGluIGBzZWxmLmRldGFpbHNgLiBBdCBsaW5lIDI2MCB0aGUgZGljdCBsaXRlcmFsIGB7XCJlcnJvclwiOiBlcnJvci5jb2RlLCBcIm1lc3NhZ2VcIjogZXJyb3IubWVzc2FnZSwgKiplcnJvci5kZXRhaWxzfWAgaXMgY29uc3RydWN0ZWQ6IHdoZW4gYGVycm9yLmRldGFpbHNgIGNvbnRhaW5zIHRoZSBrZXkgYFwiZXJyb3JcImAsIFB5dGhvbiBkaWN0IHVucGFja2luZyBzaWxlbnRseSBvdmVyd3JpdGVzIHRoZSBlYXJsaWVyIGBcImVycm9yXCI6IGVycm9yLmNvZGVgIGVudHJ5IHdpdGggdGhlIGRldGFpbCB2YWx1ZSAodGhlIEpTT04gZGVjb2RlIGVycm9yIHN0cmluZykuIFRoZSBDTEkgc3RkZXJyIG91dHB1dCBmb3IgYSBjb3JydXB0IG1hbmlmZXN0IHdvdWxkIHNob3cgdGhlIEpTT05EZWNvZGVFcnJvciBtZXNzYWdlIGFzIHRoZSBgXCJlcnJvclwiYCBmaWVsZCBpbnN0ZWFkIG9mIGBcIm1hbmlmZXN0X2ludmFsaWRcImAsIG1ha2luZyB0aGUgZXJyb3IgY29kZSB1bnJlY29nbmlzYWJsZSBieSBhbnkgY29uc3VtZXIgcGFyc2luZyB0aGF0IEpTT04uIn0= -->
| overlaid = manifest.get("overlaid_modules", []) | ||
| _require(isinstance(overlaid, list), "manifest_invalid", f"{path}: 'overlaid_modules' must be a list") | ||
| for entry in overlaid: | ||
| _require( | ||
| isinstance(entry, dict) | ||
| and isinstance(entry.get("path"), str) | ||
| and isinstance(entry.get("symbols"), list) | ||
| and bool(entry["symbols"]), | ||
| "manifest_invalid", | ||
| f"{path}: every 'overlaid_modules' entry needs a 'path' and a non-empty 'symbols' list", | ||
| path=str(path), | ||
| ) | ||
| return manifest |
There was a problem hiding this comment.
🟡 MEDIUM · security — overlaid_modules paths not confined: missing_symbols can read files outside the
load_manifest calls require_confined_entry for every entry in preserve, rejecting ../ escapes and absolute paths. But overlaid_modules entries are only type-checked (must be a str), not path-confined. missing_symbols (line 218) then does base / module.path and reads the file for AST parsing without any confinement check. A malicious or accidentally malformed manifest entry such as {"path": "../../secrets.py", "symbols": ["x"]} would cause the tool to read and parse an arbitrary file outside the package. The same code that guards preserve entries should guard overlaid_modules entries.
| overlaid = manifest.get("overlaid_modules", []) | |
| _require(isinstance(overlaid, list), "manifest_invalid", f"{path}: 'overlaid_modules' must be a list") | |
| for entry in overlaid: | |
| _require( | |
| isinstance(entry, dict) | |
| and isinstance(entry.get("path"), str) | |
| and isinstance(entry.get("symbols"), list) | |
| and bool(entry["symbols"]), | |
| "manifest_invalid", | |
| f"{path}: every 'overlaid_modules' entry needs a 'path' and a non-empty 'symbols' list", | |
| path=str(path), | |
| ) | |
| return manifest | |
| Add `require_confined_entry(entry["path"], base, path)` inside the loop at line 141, after the existing `_require` check. |
Reply @codna fix and Codna opens a verified, test-green, risk-gated fix PR.
Automated mirror of cli/ (flattened), the root allowlist (server.json, glama.json, LICENSE, .gitleaks.toml) and the public-root/ community tree from thyn-ai/codna at 54ade165c1e980a585b5e828e910c9d9cdecea23. Merges itself via auto-merge once the mirror's required checks (codna review, Analyze (python)) pass and the codna-ai review approves; if a later sync lands first, this PR is closed as superseded -- every sync PR is a full snapshot of the exported surface.