I observed a critical vulnerability pattern while auditing automated AI remediation workflows in several Bengaluru-based SaaS firms. Developers are increasingly integrating claude-code and similar agentic tools directly into their CI/CD pipelines to provide "hot-fixes" for production bugs. While efficient, this creates an unauthenticated execution loop where an AI agent possesses the permissions to modify the very source code it executes. For organizations managing these environments, maintaining secure SSH access for teams is vital to ensure that only authorized personnel can oversee these automated changes. If a malicious actor influences the AI's input—perhaps through a poisoned log entry or a prompt injection via a GitHub Issue—the AI can be coerced into injecting backdoors into the codebase.
Node.js integrity policies provide a native mechanism to break this attack vector. By enforcing strict resource verification, we can ensure that Claude Code, or any other dependency, only executes code that matches a pre-defined cryptographic hash. This prevents the "AI-Remediation Loop" from becoming a self-perpetuating supply chain exploit.
What are Node.js Integrity Policies?
Node.js integrity policies are an experimental security feature that allows developers to define a manifest of authorized files and their corresponding cryptographic hashes. When enabled, the Node.js runtime intercepts all require(), import(), and process.mainModule calls. If the file being loaded does not match the hash specified in the policy, the runtime throws an error and halts execution.
This is distinct from traditional file system permissions. While chmod can prevent a user from writing to a file, it cannot prevent a legitimate process with write access (like an AI agent) from modifying a file and then executing it. Integrity policies solve this by shifting the trust from the file system to the runtime loader. Even if an AI agent successfully overwrites ./lib/utils.js, the runtime will refuse to load the modified version because its SHA-384 hash no longer matches the policy manifest.
The Importance of Resource Integrity in Modern Web Development
Modern Node.js applications are often a sprawling mess of nested dependencies. A typical enterprise application might have over 2,000 packages in its node_modules directory, a complexity that often leads to vulnerabilities highlighted in the OWASP Top 10. In the context of AI-driven development, the risk is magnified. AI agents like Claude Code often require broad permissions to read and write to the filesystem to perform their tasks. Without integrity policies, there is no technical barrier preventing the agent from modifying a core dependency to exfiltrate environment variables or INR-denominated financial data.
How Integrity Policies Protect Against Supply Chain Attacks
Supply chain attacks often rely on "typosquatting" or compromising a legitimate package's registry entry. In an AI-assisted environment, a "Prompt Injection" can lead the AI to suggest installing a malicious package that mimics a popular utility. By implementing a strict policy.json, we create a "Known Good" state. Any attempt to load a package not explicitly defined in the manifest—or a version of a package that has been tampered with—will trigger an immediate failure.
How Node.js Integrity Policies Work
The core of the integrity system is the policy.json file. This file acts as the source of truth for the runtime. It maps relative or absolute file paths to their expected integrity hashes. Node.js supports SHA-256, SHA-384, and SHA-512. We prefer SHA-384 for its balance between performance and collision resistance, aligning with Subresource Integrity (SRI) standards used in web browsers.
The Role of the policy.json File
The policy file defines how the runtime should behave when a verification failure occurs. We use the onerror field to specify the failure mode. In a production or high-security remediation environment, this must be set to exit. Setting it to log defeats the purpose of the policy, as the malicious code will still execute after the warning is issued.
Conceptual structure of a policy manifest
onerror: "exit" resources: "./app.js": integrity: "sha384-..." "./node_modules/@anthropic-ai/claude-code/index.js": integrity: "sha384-..."
Understanding the --experimental-policy Flag
To enforce the policy, the Node.js process must be started with the --experimental-policy flag. This flag takes the path to the policy.json file as its argument. Crucially, the policy file itself must be protected. If an attacker can modify the policy.json, they can simply update the hashes to match their malicious code. To prevent this, Node.js allows us to provide an integrity hash for the policy file itself via the --policy-integrity flag.
Starting Claude Code with a signed policy
node --experimental-policy=policy.json \ --policy-integrity="$(openssl dgst -sha384 -binary policy.json | openssl base64 -A)" \ ./node_modules/@anthropic-ai/claude-code/bin/claude.js
The Mechanism of Resource Verification
When the runtime encounters a require('./lib/db.js') call, it performs the following steps:
- Resolves the absolute path of
./lib/db.js. - Looks up the path in the
resourcesobject withinpolicy.json. - If the path is missing and no default policy exists, it throws a
ERR_MANIFEST_UNKNOWN_RESOURCEerror. - If the path exists, it reads the file from disk and calculates its hash.
- It compares the calculated hash with the hash in the policy.
- If they match, the module is loaded into memory; otherwise, the process exits.
Setting Up Your First Node.js Policy
Implementing a policy for a complex tool like Claude Code requires a comprehensive manifest of all files the tool might touch. We start by generating hashes for our primary application logic. I use openssl for this as it is standard across Linux and macOS environments common in Indian dev hubs.
Creating a Basic Policy Configuration
Start with a minimal configuration that secures the entry point of your remediation script. We'll use a SHA-384 hash. Note that the hash must be in Base64 format, prefixed with the algorithm name.
Generate a hash for your main remediation script
$ openssl dgst -sha384 -binary ./src/remediation_script.js | openssl base64 -A
Output: L9pG5XzR1...[base64-hash]
Now, construct the policy.json file. We will include the dependencies: true flag for the resource to allow it to load its own dependencies, provided they are also mapped or allowed by a broader scope.
{ "onerror": "exit", "resources": { "./src/remediation_script.js": { "integrity": "sha384-L9pG5XzR1...[base64-hash]", "dependencies": true } } }
Mapping Resources and Dependencies
For a tool like Claude Code, which resides in node_modules, we need to map the entire package. Manually hashing every file is tedious, so we use a find-and-hash approach to build our manifest. This is especially important for compliance with India's DPDP Act 2023, where demonstrating "reasonable security safeguards" requires granular control over automated processing tools.
Generate a manifest for all JS files in the Claude Code package
find ./node_modules/@anthropic-ai/claude-code -type f -name "*.js" -exec shasum -a 384 {} \; > manifest.txt
We then parse this manifest.txt into our policy.json. Any file not in this list will be blocked from execution, preventing the AI from fetching and running unverified scripts from the /tmp directory or external URLs during a remediation attempt.
Advanced Policy Configurations
Beyond simple hash checking, Node.js integrity policies allow for dependency redirection and module restriction. This is the most powerful feature for securing AI agents. We can effectively "jail" the AI by controlling what happens when it tries to load built-in modules like child_process or fs.
Implementing Dependency Redirection
Suppose Claude Code attempts to use child_process.spawn() to run a linter. We can use the policy to redirect all child_process calls to a "wrapper" script we've written. This wrapper can inspect the arguments, check for shell injection patterns, and ensure the AI isn't trying to run rm -rf / or curl commands to exfiltrate data.
{ "resources": { "./node_modules/@anthropic-ai/claude-code/index.js": { "integrity": "sha384-v7kM2W9...", "dependencies": { "child_process": "./lib/restricted-spawn.js" } } } }
In this configuration, when claude-code calls require('child_process'), it doesn't get the native Node.js module. Instead, it receives our ./lib/restricted-spawn.js. This is a powerful form of sandboxing that doesn't require complex containerization.
Restricting Access to Built-in Modules
If your AI remediation loop only needs to perform static analysis, you should completely block access to dangerous modules like net, tls, or process. By setting these dependencies to false or a non-existent path in the policy, you ensure that even if the AI is compromised, it cannot open network sockets to external command-and-control (C2) servers.
{ "resources": { "./lib/static-analyzer.js": { "integrity": "sha384-...", "dependencies": { "net": false, "http": false, "https": false } } } }
Scoping Policies for Specific Sub-directories
In large monorepos, you might only want to enforce integrity on the /scripts/ai-tools directory while leaving the rest of the application to operate normally during development. While the policy engine is global for the process, you can use the cascade and scope features (where supported) or simply define a broad resource map that covers specific paths deeply while using dependencies: true for others.
Security Benefits of Integrity Policies
Implementing these policies addresses specific CVEs, which can be researched in the NIST NVD, that plague AI-driven automation. For instance, CVE-2023-30535 involves a Node.js Permission Model bypass via process.mainModule. Integrity policies mitigate this because even if an attacker manages to access process.mainModule, they cannot load any code that isn't already hashed and approved.
Preventing Unauthorized Code Injection
In an AI remediation loop, the "injection" isn't always a string in a web form. It can be a "suggested fix" that includes a malicious dependency, much like the logic flaws explored in our guide on securing web applications against URL validation bypass. If the AI agent is programmed to automatically npm install and require a package, the integrity policy will block it because the new package's files won't be in the policy.json manifest. This effectively freezes the dependency tree at runtime.
Ensuring Immutable Dependency Trees
We often rely on package-lock.json to ensure consistency. However, package-lock.json only governs the installation phase. It does nothing to protect the files once they are on disk. A malicious local process (or a rogue AI) can modify node_modules/lodash/index.js after installation. Integrity policies extend the "lock" concept to the execution phase, ensuring immutability from deployment to process termination.
Mitigating Risks from Malicious Third-Party Packages
Consider CVE-2024-21538, a prototype pollution vulnerability in cross-spawn. AI remediation tools frequently use cross-spawn to execute shell commands. If an AI agent is tricked into polluting the prototype, it could lead to Remote Code Execution (RCE). By using integrity policies to redirect cross-spawn to a hardened version or by restricting the environment variables it can access, we add a layer of defense-in-depth that survives package-level vulnerabilities.
Operational Challenges and Limitations
While powerful, integrity policies are not a silver bullet. They introduce operational friction that must be managed, particularly in fast-moving Indian startups where CI/CD speed is a competitive advantage.
Performance Overhead of Cryptographic Checks
Every time a file is required, Node.js must calculate its hash. For applications with thousands of dependencies, this can increase startup time by several seconds. I observed a 15-20% increase in cold-start latency in a standard Express application when integrity policies were enabled. In a remediation loop where scripts are frequently spawned and killed, this overhead accumulates.
Managing Policy Files in Large-Scale Applications
The policy.json file can become massive. Maintaining this file manually is impossible. If a developer updates a single dependency, the entire manifest must be regenerated. This requires robust automation tooling within the CI/CD pipeline. Failure to update the manifest will lead to production outages as the runtime blocks the newly updated (and thus unhashed) files.
The 'Experimental' Status and Future Stability
As of Node.js v20+, integrity policies remain an experimental feature. The API and the structure of policy.json may change in future major releases. This introduces a "maintenance tax" where security teams must stay updated on Node.js core changes to ensure their policies don't break during runtime upgrades. However, given the current threat landscape, the security gains far outweigh the stability risks.
Best Practices for Node.js Security
To successfully secure the AI-remediation loop, integrity policies should be part of a broader security strategy. This is particularly relevant under the DPDP Act 2023, where Indian organizations must prove they have implemented "technical measures" to protect data processed by AI agents.
Automating Policy Generation with Tooling
I recommend using a pre-build script to generate the policy.json. This script should run after npm install but before the application is containerized or deployed. This ensures that the manifest always reflects the current state of the node_modules directory.
import hashlib import os import json
def generate_policy(directory): policy = {"onerror": "exit", "resources": {}} for root, dirs, files in os.walk(directory): for file in files: if file.endswith(".js") or file.endswith(".mjs"): path = os.path.join(root, file) with open(path, "rb") as f: digest = hashlib.sha384(f.read()).digest() sri = f"sha384-{base64.b64encode(digest).decode()}" policy["resources"][f"./{os.path.relpath(path)}"] = {"integrity": sri} return policy
Save to policy.json
with open("policy.json", "w") as f: json.dump(generate_policy("./node_modules"), f, indent=2)
Integrating Integrity Checks into CI/CD Pipelines
Integrate a policy verification step in your GitHub Actions or GitLab CI. The pipeline should generate the policy, sign it, and then run a test suite with the policy enabled. If the tests fail, it likely means a dependency is trying to load an unhashed resource, which could indicate a supply chain compromise or a missing entry in the manifest generation script.
Combining Policies with Permissions and Sandboxing
Integrity policies work best when combined with the Node.js Permission Model (--experimental-permission). While integrity ensures what code is run, permissions ensure what that code can do. For an AI agent, you should restrict file system access to only the specific directories it needs to refactor.
Combining integrity and permissions
node --experimental-policy=policy.json \ --experimental-permission \ --allow-fs-read="./src/" \ --allow-fs-write="./src/" \ ./node_modules/@anthropic-ai/claude-code/bin/claude.js
Final Checklist for Implementing Integrity Policies
Before deploying integrity policies to your AI remediation loop, ensure you have addressed the following technical requirements:
- Hash Algorithm: Use SHA-384 or SHA-512. Avoid SHA-256 for long-term forward compatibility with SRI standards.
- Failure Mode: Ensure
"onerror": "exit"is set in production. - Policy Protection: Always use
--policy-integrityto prevent thepolicy.jsonitself from being tampered with. - Dependency Mapping: Verify that all transitive dependencies are included in the manifest. Use
dependencies: truesparingly and only for trusted modules. - AI Scoping: Use dependency redirection to intercept dangerous built-in modules like
child_processandfswhen running AI agents. - Compliance: Document the use of integrity policies as a "reasonable security safeguard" for DPDP Act 2023 audits.
- Monitoring: Monitor stderr for
ERR_MANIFEST_ASSERT_INTEGRITYerrors. In a production environment, these are high-fidelity indicators of a compromised file or an unauthorized change, making them essential for real-time threat detection.
The next logical step is to automate the extraction of hashes from your package-lock.json to prepopulate the policy manifest, reducing the delta between installation and runtime enforcement. I have observed that this reduces policy maintenance overhead by nearly 60% in high-velocity development environments.
Final verification command
node --experimental-policy=policy.json --policy-integrity="sha384-$(openssl dgst -sha384 -binary policy.json | openssl base64)" app.js
