import subprocess
import textwrap
import pytest
PLACEHOLDER = "<redacted value>"
def parse_env(text):
out = {}
for line in text.splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
key, _, value = line.partition("=")
out[key.strip()] = value.strip().strip('"').strip("'")
return out
def expected_env_redaction(original_text):
keys = parse_env(original_text).keys()
return "".join(f'{key}="{PLACEHOLDER}"\n' for key in keys)
def expected_key_redaction(original_text):
lines = original_text.splitlines()
return f"{lines[0]}\n{PLACEHOLDER}\n{lines[-1]}\n"
NPMRC_SECRET_SUFFIXES = ("_authtoken", "_auth", "_password")
def _is_npmrc_comment(line):
return line.lstrip().startswith(("#", ";"))
def _is_npmrc_secret_key(key):
return key.strip().lower().endswith(NPMRC_SECRET_SUFFIXES)
def parse_npmrc(text):
out = {}
for line in text.splitlines():
if _is_npmrc_comment(line) or "=" not in line:
continue
key, _, value = line.partition("=")
out[key.strip()] = value
return out
def expected_npmrc_redaction(original_text):
out = []
for line in original_text.split("\n"):
if not _is_npmrc_comment(line) and "=" in line:
key, _, _value = line.partition("=")
if _is_npmrc_secret_key(key):
out.append(f"{key}={PLACEHOLDER}")
continue
out.append(line)
return "\n".join(out)
def _raw_run(airgap_bin, *args, cwd):
return subprocess.run(
[str(airgap_bin), *args], cwd=cwd, capture_output=True, text=True
)
def test_unknown_program_is_rejected(airgap_bin, tmp_path):
result = _raw_run(airgap_bin, "cat", "/etc/hostname", cwd=tmp_path)
assert result.returncode == 1
assert "refusing to run" in result.stderr.lower()
assert result.stdout == ""
def test_allow_unknown_program_bypasses_check(airgap_bin, tmp_path):
result = _raw_run(airgap_bin, "--allow-unknown-program", "true", cwd=tmp_path)
assert "refusing to run" not in result.stderr.lower()
def test_npm_profile_denies_unapproved_file_without_tty(airgap):
result = airgap(
"cat",
".env",
airgap_flags=["--profile", "npm"],
start_new_session=True,
)
assert result.returncode != 0
assert "permission denied" in result.stderr.lower()
assert PLACEHOLDER not in result.stdout
assert "s3cr3t_pw" not in result.stdout
def test_npm_profile_allows_preapproved_path_without_prompting(airgap):
(airgap.workdir / "package.json").write_text('{"name":"demo"}\n')
result = airgap(
"cat",
"package.json",
airgap_flags=["--profile", "npm"],
start_new_session=True,
)
assert result.returncode == 0, result.stderr
assert result.stdout == '{"name":"demo"}\n'
def test_npm_profile_reads_npmrc_ungated_and_redacted(airgap):
expected = expected_npmrc_redaction((airgap.workdir / ".npmrc").read_text())
result = airgap(
"cat",
".npmrc",
airgap_flags=["--profile", "npm"],
start_new_session=True,
)
assert result.returncode == 0, result.stderr
assert result.stdout == expected
assert "npm_fake0123456789abcdefghijKLMNOPqrstuv" not in result.stdout
def test_agent_profile_redacts_without_gating(airgap):
expected = expected_env_redaction((airgap.workdir / ".env").read_text())
result = airgap(
"cat",
".env",
airgap_flags=["--profile", "agent"],
start_new_session=True,
)
assert result.returncode == 0, result.stderr
assert result.stdout == expected
def test_exit_code_propagates(airgap):
assert airgap("sh", "-c", "exit 7").returncode == 7
def test_stdout_passthrough(airgap):
result = airgap("echo", "hello-world")
assert result.returncode == 0
assert "hello-world" in result.stdout
def test_plain_file_untouched(airgap):
result = airgap("cat", "notes.txt")
assert result.returncode == 0
assert result.stdout == (airgap.workdir / "notes.txt").read_text()
@pytest.mark.parametrize("path", [".env", ".env.production"])
def test_env_read_is_exactly_redacted(airgap, path):
expected = expected_env_redaction((airgap.workdir / path).read_text())
result = airgap("cat", path)
assert result.returncode == 0
assert result.stdout == expected
def test_npmrc_read_is_exactly_redacted(airgap):
original = (airgap.workdir / ".npmrc").read_text()
result = airgap("cat", ".npmrc")
assert result.returncode == 0, result.stderr
assert result.stdout == expected_npmrc_redaction(original)
def test_npmrc_secret_values_never_leak(airgap):
result = airgap("cat", ".npmrc")
assert result.returncode == 0, result.stderr
for secret in (
"npm_fake0123456789abcdefghijKLMNOPqrstuv", "ghp_fakeGITHUBpackagestokenABCDEF123456", "czNjcjN0X3B3", "dXNlcjpzM2NyM3RfcHc=", ):
assert secret not in result.stdout
for visible in (
"registry=https://registry.npmjs.org/",
"@acme:registry=https://npm.pkg.github.com/",
"email=dev@example.com",
"always-auth=true",
):
assert visible in result.stdout
def test_npmrc_nonsecret_edit_persists_and_token_restored(airgap):
script = textwrap.dedent(
"""
lines = open('.npmrc').read().splitlines()
out = ['registry=https://example.com/' if l.startswith('registry=') else l
for l in lines]
open('.npmrc', 'w').write('\\n'.join(out) + '\\n')
"""
)
result = airgap("python3", "-c", script)
assert result.returncode == 0, result.stderr
persisted = parse_npmrc((airgap.workdir / ".npmrc").read_text())
assert persisted["registry"] == "https://example.com/"
assert (
persisted["//registry.npmjs.org/:_authToken"]
== "npm_fake0123456789abcdefghijKLMNOPqrstuv"
)
assert PLACEHOLDER not in (airgap.workdir / ".npmrc").read_text()
def test_npmrc_token_edit_persists(airgap):
script = textwrap.dedent(
"""
key = '//registry.npmjs.org/:_authToken='
lines = open('.npmrc').read().splitlines()
out = [key + 'npm_rotated' if l.startswith(key) else l for l in lines]
open('.npmrc', 'w').write('\\n'.join(out) + '\\n')
"""
)
result = airgap("python3", "-c", script)
assert result.returncode == 0, result.stderr
persisted = parse_npmrc((airgap.workdir / ".npmrc").read_text())
assert persisted["//registry.npmjs.org/:_authToken"] == "npm_rotated"
def test_symlinked_secret_outside_overlay_is_redacted(airgap, tmp_path_factory):
outside = tmp_path_factory.mktemp("dotfiles")
real = outside / ".npmrc"
real.write_text(
"registry=https://registry.npmjs.org/\n"
"//registry.npmjs.org/:_authToken=npm_SYMLINK_SECRET\n"
)
link = airgap.workdir / ".npmrc"
link.unlink() link.symlink_to(real)
result = airgap("cat", ".npmrc")
assert result.returncode == 0, result.stderr
assert "npm_SYMLINK_SECRET" not in result.stdout
assert "//registry.npmjs.org/:_authToken=<redacted value>" in result.stdout
assert "registry=https://registry.npmjs.org/" in result.stdout
def test_symlinked_env_outside_overlay_is_redacted(airgap, tmp_path_factory):
outside = tmp_path_factory.mktemp("envdir")
real = outside / ".env"
real.write_text("API_KEY=leakme-via-symlink\n")
link = airgap.workdir / ".env.linked" link.symlink_to(real)
result = airgap("cat", ".env.linked")
assert result.returncode == 0, result.stderr
assert "leakme-via-symlink" not in result.stdout
assert result.stdout == 'API_KEY="<redacted value>"\n'
def test_home_outside_cwd_is_redacted(airgap, tmp_path_factory):
home = tmp_path_factory.mktemp("fakehome")
secret = home / ".env"
secret.write_text("SECRET=topsecret\nTOKEN=abc123\n")
result = airgap("cat", str(secret), env={"HOME": str(home)})
assert result.returncode == 0, result.stderr
assert result.stdout == expected_env_redaction(secret.read_text())
NEST = textwrap.dedent(
"""
import sys, subprocess
depth, script = int(sys.argv[1]), sys.argv[2]
if depth > 0:
sys.exit(subprocess.run(
[sys.executable, "-c", script, str(depth - 1), script]
).returncode)
sys.stdout.write(open(".env").read())
"""
)
def test_deeply_nested_child_sees_redaction(airgap):
expected = expected_env_redaction((airgap.workdir / ".env").read_text())
result = airgap("python3", "-c", NEST, "8", NEST)
assert result.returncode == 0, result.stderr
assert result.stdout == expected
def test_env_edit_persists(airgap):
script = textwrap.dedent(
"""
lines = open('.env').read().splitlines()
out = ['DEBUG=false' if l.startswith('DEBUG=') else l for l in lines]
open('.env', 'w').write('\\n'.join(out) + '\\n')
"""
)
result = airgap("python3", "-c", script)
assert result.returncode == 0, result.stderr
persisted = parse_env((airgap.workdir / ".env").read_text())
assert persisted["DEBUG"] == "false"
assert "s3cr3t_pw" in persisted["DATABASE_URL"]
def test_env_add_persists(airgap):
script = textwrap.dedent(
"""
with open('.env', 'a') as f:
f.write('NEW_TOKEN=added-by-agent\\n')
"""
)
result = airgap("python3", "-c", script)
assert result.returncode == 0, result.stderr
persisted = parse_env((airgap.workdir / ".env").read_text())
assert persisted.get("NEW_TOKEN") == "added-by-agent"
assert "s3cr3t_pw" in persisted["DATABASE_URL"]
def test_env_delete_persists(airgap):
script = textwrap.dedent(
"""
lines = open('.env').read().splitlines()
out = [l for l in lines if not l.startswith('API_KEY=')]
open('.env', 'w').write('\\n'.join(out) + '\\n')
"""
)
result = airgap("python3", "-c", script)
assert result.returncode == 0, result.stderr
persisted = parse_env((airgap.workdir / ".env").read_text())
assert "API_KEY" not in persisted
assert "DATABASE_URL" in persisted
@pytest.mark.parametrize("path", ["id_rsa", "id_ed25519", "secret.asc"])
def test_private_key_redacted(airgap, path):
expected = expected_key_redaction((airgap.workdir / path).read_text())
result = airgap("cat", path)
assert result.returncode == 0
assert result.stdout == expected