{"slug": "a-deny-by-default-manifest-for-ai-generated-services-on-a-free-server", "title": "A Deny-by-Default Manifest for AI-Generated Services on a Free Server", "summary": "MonkeyCode, as part of its product outreach, has published a deny-by-default manifest and a static analysis preflight script to help developers secure AI-generated services deployed on free servers. The approach treats a free server as a real security boundary by requiring a capability contract before deployment, with checks for banned imports and file access outside declared paths. The manifest includes explicit listen ports, filesystem read/write paths, empty outbound network hosts, and environment variable denials.", "body_md": "Why this is worth reading: a free model can hand you a working service in one prompt, and a free server can publish it before you have read the control flow. The cheap path tends to skip the permissions conversation. This article gives you a deny-by-default manifest and a bubblewrap preflight so you can treat a free server as a real boundary without buying extra infrastructure.\n\nIf you are using MonkeyCode's free model access and free server option, the convenience is real, not an excuse to skip host hygiene. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I am not going to assume model names, quotas, or how long the free server stays alive; the manifest below works whether your process is a shared worker or an isolated container.\n\nYou ask a free model for a small service and it returns code that looks finished. You deploy it to the free server because the cost of trying is nearly zero, and you skip the review that a paid environment would force you to schedule. A free tier lowers the cost of exploration, but it also lowers the cost of not declaring what the service may touch. A log write to `/tmp`\n\nbecomes a write to shared disk, an outbound call to an analytics endpoint creates a dependency you did not choose, and a stray `open()`\n\ncan read a config file that the service never needed. The fix is to write a capability contract before you deploy, then make the preflight fail on any undeclared capability.\n\nYou can ask the model to suggest a manifest, but you should be the one to commit it. A server contract is a review artifact, not a generation artifact. Here is the smallest version that is still useful:\n\n```\n{\n  \"service\": \"weather-echo\",\n  \"entrypoint\": \"python app.py\",\n  \"listen\": {\"ports\": [8080], \"bind\": \"127.0.0.1\"},\n  \"filesystem\": {\n    \"read\": [\"data/config.json\"],\n    \"write\": [\"data/cache/\"]\n  },\n  \"network\": {\n    \"outbound_hosts\": []\n  },\n  \"env\": {\n    \"read\": [\"PORT\"],\n    \"deny\": [\"DATABASE_URL\", \"SECRET_KEY\", \"AWS_*\"]\n  },\n  \"resources\": {\n    \"max_cpu_seconds\": 10,\n    \"max_memory_mb\": 128\n  }\n}\n```\n\nYou keep `listen`\n\nexplicit because a free server often provides a port through an environment variable and nothing else. You keep `filesystem`\n\nexplicit because a shared free process can still write to the working directory. You keep `network.outbound_hosts`\n\nempty by default because a simple service should not start with full egress. The `resources`\n\nkeys are aspirational on some free tiers, but they still record the ceiling you intended to enforce.\n\nStatic analysis is not a security boundary, but it catches the obvious undeclared imports and literal file paths before you spend time on a runtime sandbox. The following checker reads `server-contract.json`\n\n, parses the target Python file, and fails on banned modules or `open()`\n\ncalls outside the allowed paths.\n\n``` python\n#!/usr/bin/env python3\nimport ast\nimport json\nimport os\nimport sys\n\ncontract = json.load(open('server-contract.json'))\nread_paths = {os.path.abspath(p) for p in contract['filesystem']['read']}\nwrite_paths = {os.path.abspath(p.rstrip('/')) for p in contract['filesystem']['write']}\nbanned_modules = {'subprocess', 'socket', 'requests', 'urllib.request'}\n\ndef check_file(path):\n    tree = ast.parse(open(path, encoding='utf-8').read(), filename=path)\n    for node in ast.walk(tree):\n        if isinstance(node, ast.Import):\n            for alias in node.names:\n                assert alias.name.split('.')[0] not in banned_modules, (\n                    f'banned import {alias.name} in {path}'\n                )\n        if isinstance(node, ast.ImportFrom):\n            assert node.module.split('.')[0] not in banned_modules, (\n                f'banned import from {node.module} in {path}'\n            )\n        if isinstance(node, ast.Call) and getattr(node.func, 'id', None) == 'open':\n            if node.args and isinstance(node.args[0], ast.Constant):\n                target = os.path.abspath(node.args[0].value)\n                mode = node.args[1].value if len(node.args) > 1 and isinstance(node.args[1], ast.Constant) else 'r'\n                allowed = write_paths if any(m in mode for m in ('w', 'a', 'x', '+')) else read_paths | write_paths\n                assert target in allowed or any(target.startswith(p + os.sep) for p in allowed), (\n                    f'open outside manifest: {target} in {path}'\n                )\n\nif __name__ == '__main__':\n    for path in sys.argv[1:]:\n        check_file(path)\n    print('static contract check passed')\n```\n\nYou run it with `python check_contract.py app.py`\n\n. The script does not understand dynamic paths or indirect imports, so a clean result means only that there is less obvious surface to review. It is a first filter, not a finding of safety.\n\nYou can use `bubblewrap`\n\non Linux to create a small sandbox with a read-only application directory and a single writable data directory. The command below drops user, PID, IPC, and UTS namespaces while sharing the network with the host, so you can still smoke-test the local port.\n\n``` bash\n#!/usr/bin/env bash\nset -euo pipefail\n\npython check_contract.py app.py\n\nmkdir -p data\n\nbwrap --ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind \"$(pwd)\" /app --bind \"$(pwd)/data\" /app/data --dev /dev --proc /proc --chdir /app --unshare-user --unshare-pid --unshare-ipc --unshare-uts --die-with-parent python app.py &\npid=$!\n\ntrap 'kill \"$pid\" 2>/dev/null || true' EXIT\nsleep 1\ncurl -sSf http://127.0.0.1:8080/health\necho 'preflight passed'\n```\n\nYou may need to adjust the `/usr`\n\n, `/lib`\n\n, and `/lib64`\n\nbind mounts for your distro or base image. The point of the command is not to make every service portable; the point is to make the intended writable surface explicit and to stop the service from carrying more privileges than the manifest declares.\n\nYou should verify the gate by adding a known violation before you trust it. Change one line in `app.py`\n\nso it writes to `/tmp/canary`\n\n, then run `python check_contract.py app.py`\n\nand watch it fail. Then try running a network attempt inside a network namespace you did not share:\n\n``` python\nbwrap --unshare-net python -c \"import socket; socket.create_connection(('example.com', 80), timeout=2)\"\n```\n\nIf the command fails to connect, the network isolation is doing its job. If it succeeds, your wrapper is not applying the flag you expected and the preflight is not a real gate. A passing test that you have never forced to fail is just a ritual.\n\nYou should not read this as a replacement for platform-level enforcement. Static analysis misses dynamic file paths and imports; `bubblewrap`\n\ndoes not enforce an outbound-host allowlist when you share the host network; and a free server platform may run your process with different user, network, or filesystem policies than your laptop. The manifest is a statement of intent that you still need to enforce on the platform or in a real container with a network policy. It also does not prove the generated code is correct or safe; it only catches capability drift you already decided to reject.\n\nYou should skip this approach if the service touches customer data, regulated data, multi-tenant workloads, or anything with an availability obligation. In those cases you need a maintained container or cluster with network policies, not a free server and a local sandbox. You should also skip it if the service legitimately needs wide outbound access; a deny-by-default network section will fight you on every upstream change and may push you to weaken the manifest instead of tighten the design.\n\nWhen you next stand up a service generated by a free model on a free server, write the manifest first. If MonkeyCode's free server option is the destination, keep that manifest in your repository, not in the platform defaults. The gate should fail before your public endpoint does.", "url": "https://wpnews.pro/news/a-deny-by-default-manifest-for-ai-generated-services-on-a-free-server", "canonical_source": "https://dev.to/github_7727/a-deny-by-default-manifest-for-ai-generated-services-on-a-free-server-49ko", "published_at": "2026-08-16 02:11:19+00:00", "updated_at": "2026-08-16 02:40:56.560509+00:00", "lang": "en", "topics": ["ai-products", "developer-tools", "ai-safety"], "entities": ["MonkeyCode"], "alternates": {"html": "https://wpnews.pro/news/a-deny-by-default-manifest-for-ai-generated-services-on-a-free-server", "markdown": "https://wpnews.pro/news/a-deny-by-default-manifest-for-ai-generated-services-on-a-free-server.md", "text": "https://wpnews.pro/news/a-deny-by-default-manifest-for-ai-generated-services-on-a-free-server.txt", "jsonld": "https://wpnews.pro/news/a-deny-by-default-manifest-for-ai-generated-services-on-a-free-server.jsonld"}}