)]}'
{
  "commit": "3735d9eb08685ef5a64f82042ad2107872386cf0",
  "tree": "687199417b0f0fb98a09ecb96b7e6159bebe2719",
  "parents": [
    "04338034e3db95093b6ab1ebc69baa74eb784427"
  ],
  "author": {
    "name": "deacon-mp",
    "email": "61169193+deacon-mp@users.noreply.github.com",
    "time": "Mon May 18 19:43:03 2026 -0400"
  },
  "committer": {
    "name": "GitHub",
    "email": "noreply@github.com",
    "time": "Mon May 18 19:43:03 2026 -0400"
  },
  "message": "security(file_svc): containment check on save_file to block agent-contact path traversal (#3380)\n\n* security(file_svc): containment check on save_file to block path traversal\n\nfile_svc.save_file builds its target path as os.path.join(target_dir, filename)\nand writes payload bytes there with no validation of the resulting location.\nIt is reached from the agent contact handlers with an attacker-controlled\nfilename:\n\n  * app/contacts/contact_ftp.py:217  — FTP submit_uploaded_file\n  * app/contacts/contact_dns.py:491  — DNS _submit_uploaded_file\n  * app/contacts/contact_gist.py:212 — Gist contact upload\n  * app/contacts/contact_slack.py:206 — Slack contact upload\n\nA filename of e.g. \u0027../../data/object_store\u0027 escapes the exfil tree and lets\nan attacker write arbitrary bytes to any path the server has permission to\nwrite. data_svc.restore_state pickle.loads(\u0027data/object_store\u0027) on the next\nrestart, which turns that write primitive into RCE on the team server. The\nfile is Fernet-encrypted with conf encryption_key, but with the documented\ndefault encryption_key\u003dADMIN123 a remote attacker can forge a valid blob.\n\nsave_file has two legitimate call modes:\n  (1) target_dir + basename — sink mode used by data_svc, app_svc, c_operation,\n      base_knowledge_svc, and the four agent contact handlers above.\n  (2) \u0027\u0027           + relative-path — path mode used by ability_api_manager,\n      base_api_manager, and rest_svc, which assemble paths from sanitised IDs.\n\nA simple \"filename must be a basename\" check breaks mode (2). Instead, resolve\nthe final path with realpath and require it to stay under its parent. This:\n\n  * rejects \u0027..\u0027 / absolute-path escape in BOTH call modes, with one check;\n  * leaves all in-tree callers\u0027 happy paths intact (object_store, contact\n    reports, operation logs, ability/source YAML writes);\n  * provides defense-in-depth for any future caller, not just the four contact\n    handlers known to be vulnerable today.\n\nIncludes a regression test that exercises three traversal vectors and\nconfirms each is rejected before any bytes are written.\n\nReported externally on 2026-05-18 alongside the broader audit pass.\n\n* test(file_svc): fix path-traversal regression test guard\n\nThe post-condition assertion \u0027not os.path.exists(realpath(...))\u0027 used\n\u0027/etc/passwd\u0027 as one of the resolved-traversal targets. That file exists\non every Linux runner, so the guard would fail even when the production\ncode correctly raised ValueError before any I/O — what we saw in CI.\n\nThe security check (pytest.raises(ValueError, match\u003d\u0027escapes parent\u0027))\nis unchanged; it\u0027s the only assertion that proves the fix works. The\npost-condition guard is now scoped to canary basenames that cannot\npre-exist by accident, so it actually exercises \u0027rejected before write\u0027\nwithout depending on system-file absence.\n\n---------\n\nCo-authored-by: deacon-mp \u003cmperry@mitre.org\u003e",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "ae032b4a02ab1b2ed8c56d691a07a511e0d709ca",
      "old_mode": 33188,
      "old_path": "app/service/file_svc.py",
      "new_id": "c8d319696a004b16aad6051a9d0800458e538042",
      "new_mode": 33188,
      "new_path": "app/service/file_svc.py"
    },
    {
      "type": "modify",
      "old_id": "88710937f23f890e6184b73e69ee46339af5c657",
      "old_mode": 33188,
      "old_path": "tests/services/test_file_svc.py",
      "new_id": "97080d8e14561ed141865c6512cf8efde4386a37",
      "new_mode": 33188,
      "new_path": "tests/services/test_file_svc.py"
    }
  ]
}
