This commit is contained in:
Eli Peter 2026-06-05 10:16:30 -05:00 committed by GitHub
parent 55247b7fcd
commit 991c84a1eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1464 changed files with 225448 additions and 1985 deletions

View file

@ -0,0 +1,22 @@
"""Phase 12 — async coroutine, benign."""
import asyncio
import re
import subprocess
_VALID_HOST = re.compile(r"^[A-Za-z0-9.-]{1,253}$")
async def run_ping(host):
await asyncio.sleep(0)
if not _VALID_HOST.fullmatch(host or ""):
print("invalid host")
return
result = subprocess.run(
["ping", "-c", "1", host],
shell=False,
capture_output=True,
text=True,
timeout=5,
)
print(result.stdout)
print(result.stderr, end="")

View file

@ -0,0 +1,21 @@
"""Phase 12 — async coroutine, vulnerable.
`async def` coroutine that shells out with concatenated user input.
Nyx harness wraps the call in `asyncio.run`.
"""
import asyncio
import subprocess
async def run_ping(host):
"""Vulnerable async coroutine."""
await asyncio.sleep(0)
result = subprocess.run(
"ping -c 1 " + host,
shell=True,
capture_output=True,
text=True,
timeout=5,
)
print(result.stdout)
print(result.stderr, end="")

View file

@ -0,0 +1,223 @@
#!/usr/bin/env python3
"""Nyx dynamic harness — auto-generated, do not edit."""
import os
import sys
import traceback
# ── Sink-reachability probe (sys.settrace) ────────────────────────────────────
# ── __nyx_probe shim (Phase 06 — Track C.1, Phase 08 — Track C.4 + C.5) ──────
# Deny-substring list mirrors crate::dynamic::policy::DENY_KEY_SUBSTRINGS; keep
# in sync when the host-side policy gains new entries.
_NYX_DENY_SUBSTRINGS = (
"TOKEN", "SECRET", "PASSWORD", "PASSWD", "API_KEY", "APIKEY",
"PRIVATE_KEY", "CREDENTIAL", "SESSION", "COOKIE", "AUTH", "BEARER",
"AWS_ACCESS", "AWS_SESSION", "GH_TOKEN", "GITHUB_TOKEN", "NPM_TOKEN",
"PYPI_TOKEN", "DOCKER_PASS",
)
_NYX_PAYLOAD_LIMIT = 16 * 1024
_NYX_REDACTED = "<redacted-by-nyx-policy>"
def __nyx_scrub_env():
import os
out = {}
for k, v in os.environ.items():
ku = str(k).upper()
if any(n in ku for n in _NYX_DENY_SUBSTRINGS):
out[k] = _NYX_REDACTED
else:
out[k] = v
return out
def __nyx_witness(sink_callee, args):
import os
payload = os.environ.get("NYX_PAYLOAD", "")
payload_bytes = payload.encode("utf-8", "replace") if isinstance(payload, str) else bytes(payload)
if len(payload_bytes) > _NYX_PAYLOAD_LIMIT:
payload_bytes = payload_bytes[:_NYX_PAYLOAD_LIMIT]
args_repr = []
for a in args:
if isinstance(a, (bytes, bytearray)):
args_repr.append("<bytes:%d>" % len(a))
else:
args_repr.append(str(a))
try:
cwd = os.getcwd()
except OSError:
cwd = ""
return {
"env_snapshot": __nyx_scrub_env(),
"cwd": cwd,
"payload_bytes": list(payload_bytes),
"callee": str(sink_callee),
"args_repr": args_repr,
}
def __nyx_emit(rec):
import os, json
p = os.environ.get("NYX_PROBE_PATH")
if not p:
return
try:
with open(p, "a") as _f:
_f.write(json.dumps(rec) + "\n")
except OSError:
pass
def __nyx_probe(sink_callee, *args):
import os, time
serialised = []
for a in args:
if isinstance(a, (bytes, bytearray)):
serialised.append({"kind": "Bytes", "value": list(a)})
elif isinstance(a, bool):
serialised.append({"kind": "Int", "value": 1 if a else 0})
elif isinstance(a, int):
serialised.append({"kind": "Int", "value": a})
else:
serialised.append({"kind": "String", "value": str(a)})
rec = {
"sink_callee": str(sink_callee),
"args": serialised,
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Normal"},
"witness": __nyx_witness(sink_callee, args),
}
__nyx_emit(rec)
# Phase 08: sink-site signal handler. Call __nyx_install_crash_guard before
# invoking the instrumented sink so a SIGSEGV / SIGABRT / etc. is captured as
# a Crash probe (with witness) before the process aborts. The shim re-raises
# the signal on the default handler after writing so process-level outcome
# observers (exit_code) still see the death.
_NYX_SIGNAL_NAMES = {}
def __nyx_install_crash_guard(sink_callee):
import signal, os, time
catchable = []
for nm in ("SIGSEGV", "SIGABRT", "SIGBUS", "SIGFPE", "SIGILL"):
s = getattr(signal, nm, None)
if s is not None:
catchable.append((nm, s))
_NYX_SIGNAL_NAMES[s] = nm
def _handler(signum, frame):
nm = _NYX_SIGNAL_NAMES.get(signum, "SIG?")
rec = {
"sink_callee": str(sink_callee),
"args": [],
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Crash", "signal": nm},
"witness": __nyx_witness(sink_callee, []),
}
__nyx_emit(rec)
# Reset to default and re-raise so the process actually dies.
signal.signal(signum, signal.SIG_DFL)
os.kill(os.getpid(), signum)
for _nm, s in catchable:
try:
signal.signal(s, _handler)
except (OSError, ValueError):
pass
# Phase 10 (Track D.3) stub helpers. When the verifier spawned a SqlStub it
# publishes the queries-log path through NYX_SQL_LOG; a sink call site that
# wants the host-side stub to see its query appends one record-per-call. The
# helper is a no-op when NYX_SQL_LOG is unset so the same fixture source still
# runs under harness modes that didn't spawn a stub.
def __nyx_stub_sql_record(query, **detail):
import os
p = os.environ.get("NYX_SQL_LOG")
if not p:
return
try:
with open(p, "a") as _f:
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write(str(query))
if not str(query).endswith('\n'):
_f.write('\n')
except OSError:
pass
# Phase 10 (Track D.3) HTTP recording helper. When the verifier spawned an
# HttpStub it publishes the side-channel log path through NYX_HTTP_LOG; a
# sink call site whose outbound request never reaches the on-the-wire
# listener (DNS-mocked, network-isolated sandbox, pre-flight check) can
# call this helper to surface the attempted call. Format matches the SQL
# helper so the host-side merger parses both streams identically.
def __nyx_stub_http_record(method, url, body=None, **detail):
import os
p = os.environ.get("NYX_HTTP_LOG")
if not p:
return
try:
with open(p, "a") as _f:
_f.write('# method: %s\n' % str(method))
_f.write('# url: %s\n' % str(url))
if body is not None:
_f.write('# body: %s\n' % str(body))
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write('%s %s\n' % (str(method), str(url)))
except OSError:
pass
_NYX_SINK_FILE = "<TMPDIR>/<ENTRY_FILE>"
_NYX_SINK_LINE = 13
_NYX_SINK_HIT = False
def _nyx_tracer(frame, event, arg):
global _NYX_SINK_HIT
if not _NYX_SINK_HIT and event == "line":
fname = frame.f_code.co_filename
if fname == _NYX_SINK_FILE or fname.endswith(_NYX_SINK_FILE) or (
os.path.basename(fname) == os.path.basename(_NYX_SINK_FILE)
):
if _NYX_SINK_LINE <= frame.f_lineno <= _NYX_SINK_LINE + 5:
_NYX_SINK_HIT = True
print("__NYX_SINK_HIT__", flush=True)
return _nyx_tracer
sys.settrace(_nyx_tracer)
# ── Payload loading ────────────────────────────────────────────────────────────
_payload_raw = os.environb.get(b"NYX_PAYLOAD", b"")
if not _payload_raw:
import base64
_payload_b64 = os.environ.get("NYX_PAYLOAD_B64", "")
if _payload_b64:
_payload_raw = base64.b64decode(_payload_b64)
try:
payload = _payload_raw.decode("utf-8")
except UnicodeDecodeError:
payload = _payload_raw.decode("latin-1")
# ── Entry module import ────────────────────────────────────────────────────────
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ".")
try:
import vuln as _entry_mod
except ImportError as _e:
print(f"NYX_IMPORT_ERROR: {_e}", file=sys.stderr, flush=True)
sys.exit(77)
# Shape: async coroutine — wrap in asyncio.run.
import asyncio
try:
_coro = _entry_mod.run_ping(payload)
_result = asyncio.run(_coro)
if _result is not None:
try:
print(str(_result), flush=True)
except Exception:
pass
except SystemExit as _e:
sys.exit(_e.code)
except Exception as _e:
print(f"NYX_EXCEPTION: {type(_e).__name__}: {_e}", file=sys.stderr, flush=True)
sys.settrace(None)

View file

@ -0,0 +1,25 @@
"""Phase 12 — Celery task, benign."""
import re
import subprocess
from celery import Celery
app = Celery("nyx_fixture")
_VALID_HOST = re.compile(r"^[A-Za-z0-9.-]{1,253}$")
@app.task
def run_job(host):
if not _VALID_HOST.fullmatch(host or ""):
print("invalid host")
return
result = subprocess.run(
["ping", "-c", "1", host],
shell=False,
capture_output=True,
text=True,
timeout=5,
)
print(result.stdout)
print(result.stderr, end="")

View file

@ -0,0 +1,25 @@
"""Phase 12 — Celery task, vulnerable.
Celery's `@app.task` decorator wraps the underlying function on a Task
object. Nyx harness reaches the inner callable via `.run` /
`.__wrapped__` so no broker is required.
"""
import subprocess
from celery import Celery
app = Celery("nyx_fixture")
@app.task
def run_job(host):
"""Vulnerable Celery task body."""
result = subprocess.run(
"ping -c 1 " + host,
shell=True,
capture_output=True,
text=True,
timeout=5,
)
print(result.stdout)
print(result.stderr, end="")

View file

@ -0,0 +1,226 @@
#!/usr/bin/env python3
"""Nyx dynamic harness — auto-generated, do not edit."""
import os
import sys
import traceback
# ── Sink-reachability probe (sys.settrace) ────────────────────────────────────
# ── __nyx_probe shim (Phase 06 — Track C.1, Phase 08 — Track C.4 + C.5) ──────
# Deny-substring list mirrors crate::dynamic::policy::DENY_KEY_SUBSTRINGS; keep
# in sync when the host-side policy gains new entries.
_NYX_DENY_SUBSTRINGS = (
"TOKEN", "SECRET", "PASSWORD", "PASSWD", "API_KEY", "APIKEY",
"PRIVATE_KEY", "CREDENTIAL", "SESSION", "COOKIE", "AUTH", "BEARER",
"AWS_ACCESS", "AWS_SESSION", "GH_TOKEN", "GITHUB_TOKEN", "NPM_TOKEN",
"PYPI_TOKEN", "DOCKER_PASS",
)
_NYX_PAYLOAD_LIMIT = 16 * 1024
_NYX_REDACTED = "<redacted-by-nyx-policy>"
def __nyx_scrub_env():
import os
out = {}
for k, v in os.environ.items():
ku = str(k).upper()
if any(n in ku for n in _NYX_DENY_SUBSTRINGS):
out[k] = _NYX_REDACTED
else:
out[k] = v
return out
def __nyx_witness(sink_callee, args):
import os
payload = os.environ.get("NYX_PAYLOAD", "")
payload_bytes = payload.encode("utf-8", "replace") if isinstance(payload, str) else bytes(payload)
if len(payload_bytes) > _NYX_PAYLOAD_LIMIT:
payload_bytes = payload_bytes[:_NYX_PAYLOAD_LIMIT]
args_repr = []
for a in args:
if isinstance(a, (bytes, bytearray)):
args_repr.append("<bytes:%d>" % len(a))
else:
args_repr.append(str(a))
try:
cwd = os.getcwd()
except OSError:
cwd = ""
return {
"env_snapshot": __nyx_scrub_env(),
"cwd": cwd,
"payload_bytes": list(payload_bytes),
"callee": str(sink_callee),
"args_repr": args_repr,
}
def __nyx_emit(rec):
import os, json
p = os.environ.get("NYX_PROBE_PATH")
if not p:
return
try:
with open(p, "a") as _f:
_f.write(json.dumps(rec) + "\n")
except OSError:
pass
def __nyx_probe(sink_callee, *args):
import os, time
serialised = []
for a in args:
if isinstance(a, (bytes, bytearray)):
serialised.append({"kind": "Bytes", "value": list(a)})
elif isinstance(a, bool):
serialised.append({"kind": "Int", "value": 1 if a else 0})
elif isinstance(a, int):
serialised.append({"kind": "Int", "value": a})
else:
serialised.append({"kind": "String", "value": str(a)})
rec = {
"sink_callee": str(sink_callee),
"args": serialised,
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Normal"},
"witness": __nyx_witness(sink_callee, args),
}
__nyx_emit(rec)
# Phase 08: sink-site signal handler. Call __nyx_install_crash_guard before
# invoking the instrumented sink so a SIGSEGV / SIGABRT / etc. is captured as
# a Crash probe (with witness) before the process aborts. The shim re-raises
# the signal on the default handler after writing so process-level outcome
# observers (exit_code) still see the death.
_NYX_SIGNAL_NAMES = {}
def __nyx_install_crash_guard(sink_callee):
import signal, os, time
catchable = []
for nm in ("SIGSEGV", "SIGABRT", "SIGBUS", "SIGFPE", "SIGILL"):
s = getattr(signal, nm, None)
if s is not None:
catchable.append((nm, s))
_NYX_SIGNAL_NAMES[s] = nm
def _handler(signum, frame):
nm = _NYX_SIGNAL_NAMES.get(signum, "SIG?")
rec = {
"sink_callee": str(sink_callee),
"args": [],
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Crash", "signal": nm},
"witness": __nyx_witness(sink_callee, []),
}
__nyx_emit(rec)
# Reset to default and re-raise so the process actually dies.
signal.signal(signum, signal.SIG_DFL)
os.kill(os.getpid(), signum)
for _nm, s in catchable:
try:
signal.signal(s, _handler)
except (OSError, ValueError):
pass
# Phase 10 (Track D.3) stub helpers. When the verifier spawned a SqlStub it
# publishes the queries-log path through NYX_SQL_LOG; a sink call site that
# wants the host-side stub to see its query appends one record-per-call. The
# helper is a no-op when NYX_SQL_LOG is unset so the same fixture source still
# runs under harness modes that didn't spawn a stub.
def __nyx_stub_sql_record(query, **detail):
import os
p = os.environ.get("NYX_SQL_LOG")
if not p:
return
try:
with open(p, "a") as _f:
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write(str(query))
if not str(query).endswith('\n'):
_f.write('\n')
except OSError:
pass
# Phase 10 (Track D.3) HTTP recording helper. When the verifier spawned an
# HttpStub it publishes the side-channel log path through NYX_HTTP_LOG; a
# sink call site whose outbound request never reaches the on-the-wire
# listener (DNS-mocked, network-isolated sandbox, pre-flight check) can
# call this helper to surface the attempted call. Format matches the SQL
# helper so the host-side merger parses both streams identically.
def __nyx_stub_http_record(method, url, body=None, **detail):
import os
p = os.environ.get("NYX_HTTP_LOG")
if not p:
return
try:
with open(p, "a") as _f:
_f.write('# method: %s\n' % str(method))
_f.write('# url: %s\n' % str(url))
if body is not None:
_f.write('# body: %s\n' % str(body))
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write('%s %s\n' % (str(method), str(url)))
except OSError:
pass
_NYX_SINK_FILE = "<TMPDIR>/<ENTRY_FILE>"
_NYX_SINK_LINE = 17
_NYX_SINK_HIT = False
def _nyx_tracer(frame, event, arg):
global _NYX_SINK_HIT
if not _NYX_SINK_HIT and event == "line":
fname = frame.f_code.co_filename
if fname == _NYX_SINK_FILE or fname.endswith(_NYX_SINK_FILE) or (
os.path.basename(fname) == os.path.basename(_NYX_SINK_FILE)
):
if _NYX_SINK_LINE <= frame.f_lineno <= _NYX_SINK_LINE + 5:
_NYX_SINK_HIT = True
print("__NYX_SINK_HIT__", flush=True)
return _nyx_tracer
sys.settrace(_nyx_tracer)
# ── Payload loading ────────────────────────────────────────────────────────────
_payload_raw = os.environb.get(b"NYX_PAYLOAD", b"")
if not _payload_raw:
import base64
_payload_b64 = os.environ.get("NYX_PAYLOAD_B64", "")
if _payload_b64:
_payload_raw = base64.b64decode(_payload_b64)
try:
payload = _payload_raw.decode("utf-8")
except UnicodeDecodeError:
payload = _payload_raw.decode("latin-1")
# ── Entry module import ────────────────────────────────────────────────────────
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ".")
try:
import vuln as _entry_mod
except ImportError as _e:
print(f"NYX_IMPORT_ERROR: {_e}", file=sys.stderr, flush=True)
sys.exit(77)
# Shape: Celery task — call underlying function directly (eager).
try:
_task = _entry_mod.run_job
# Celery tasks expose the underlying function via `.run` (always) and
# `.__wrapped__` (when the decorator preserves it). Prefer the
# underlying callable so we don't go through Celery's broker.
_fn = getattr(_task, "run", None) or getattr(_task, "__wrapped__", None) or _task
_result = _fn(payload)
if _result is not None:
try:
print(str(_result), flush=True)
except Exception:
pass
except SystemExit as _e:
sys.exit(_e.code)
except Exception as _e:
print(f"NYX_EXCEPTION: {type(_e).__name__}: {_e}", file=sys.stderr, flush=True)
sys.settrace(None)

View file

@ -0,0 +1,26 @@
"""Phase 12 — CLI shape, benign."""
import re
import subprocess
import sys
_VALID_HOST = re.compile(r"^[A-Za-z0-9.-]{1,253}$")
def main():
host = sys.argv[1] if len(sys.argv) > 1 else ""
if not _VALID_HOST.fullmatch(host):
print("invalid host")
return
result = subprocess.run(
["ping", "-c", "1", host],
shell=False,
capture_output=True,
text=True,
timeout=5,
)
print(result.stdout)
print(result.stderr, end="")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,26 @@
"""Phase 12 — CLI shape, vulnerable.
Driven via `if __name__ == "__main__":` Nyx harness sets
`sys.argv[1]` to the payload and either calls `main()` or
`runpy.run_module(..., run_name="__main__")` to fire the guard block.
"""
import subprocess
import sys
def main():
"""Vulnerable: read host from argv[1] and shell out."""
host = sys.argv[1] if len(sys.argv) > 1 else ""
result = subprocess.run(
"ping -c 1 " + host,
shell=True,
capture_output=True,
text=True,
timeout=5,
)
print(result.stdout)
print(result.stderr, end="")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,231 @@
#!/usr/bin/env python3
"""Nyx dynamic harness — auto-generated, do not edit."""
import os
import sys
import traceback
# ── Sink-reachability probe (sys.settrace) ────────────────────────────────────
# ── __nyx_probe shim (Phase 06 — Track C.1, Phase 08 — Track C.4 + C.5) ──────
# Deny-substring list mirrors crate::dynamic::policy::DENY_KEY_SUBSTRINGS; keep
# in sync when the host-side policy gains new entries.
_NYX_DENY_SUBSTRINGS = (
"TOKEN", "SECRET", "PASSWORD", "PASSWD", "API_KEY", "APIKEY",
"PRIVATE_KEY", "CREDENTIAL", "SESSION", "COOKIE", "AUTH", "BEARER",
"AWS_ACCESS", "AWS_SESSION", "GH_TOKEN", "GITHUB_TOKEN", "NPM_TOKEN",
"PYPI_TOKEN", "DOCKER_PASS",
)
_NYX_PAYLOAD_LIMIT = 16 * 1024
_NYX_REDACTED = "<redacted-by-nyx-policy>"
def __nyx_scrub_env():
import os
out = {}
for k, v in os.environ.items():
ku = str(k).upper()
if any(n in ku for n in _NYX_DENY_SUBSTRINGS):
out[k] = _NYX_REDACTED
else:
out[k] = v
return out
def __nyx_witness(sink_callee, args):
import os
payload = os.environ.get("NYX_PAYLOAD", "")
payload_bytes = payload.encode("utf-8", "replace") if isinstance(payload, str) else bytes(payload)
if len(payload_bytes) > _NYX_PAYLOAD_LIMIT:
payload_bytes = payload_bytes[:_NYX_PAYLOAD_LIMIT]
args_repr = []
for a in args:
if isinstance(a, (bytes, bytearray)):
args_repr.append("<bytes:%d>" % len(a))
else:
args_repr.append(str(a))
try:
cwd = os.getcwd()
except OSError:
cwd = ""
return {
"env_snapshot": __nyx_scrub_env(),
"cwd": cwd,
"payload_bytes": list(payload_bytes),
"callee": str(sink_callee),
"args_repr": args_repr,
}
def __nyx_emit(rec):
import os, json
p = os.environ.get("NYX_PROBE_PATH")
if not p:
return
try:
with open(p, "a") as _f:
_f.write(json.dumps(rec) + "\n")
except OSError:
pass
def __nyx_probe(sink_callee, *args):
import os, time
serialised = []
for a in args:
if isinstance(a, (bytes, bytearray)):
serialised.append({"kind": "Bytes", "value": list(a)})
elif isinstance(a, bool):
serialised.append({"kind": "Int", "value": 1 if a else 0})
elif isinstance(a, int):
serialised.append({"kind": "Int", "value": a})
else:
serialised.append({"kind": "String", "value": str(a)})
rec = {
"sink_callee": str(sink_callee),
"args": serialised,
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Normal"},
"witness": __nyx_witness(sink_callee, args),
}
__nyx_emit(rec)
# Phase 08: sink-site signal handler. Call __nyx_install_crash_guard before
# invoking the instrumented sink so a SIGSEGV / SIGABRT / etc. is captured as
# a Crash probe (with witness) before the process aborts. The shim re-raises
# the signal on the default handler after writing so process-level outcome
# observers (exit_code) still see the death.
_NYX_SIGNAL_NAMES = {}
def __nyx_install_crash_guard(sink_callee):
import signal, os, time
catchable = []
for nm in ("SIGSEGV", "SIGABRT", "SIGBUS", "SIGFPE", "SIGILL"):
s = getattr(signal, nm, None)
if s is not None:
catchable.append((nm, s))
_NYX_SIGNAL_NAMES[s] = nm
def _handler(signum, frame):
nm = _NYX_SIGNAL_NAMES.get(signum, "SIG?")
rec = {
"sink_callee": str(sink_callee),
"args": [],
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Crash", "signal": nm},
"witness": __nyx_witness(sink_callee, []),
}
__nyx_emit(rec)
# Reset to default and re-raise so the process actually dies.
signal.signal(signum, signal.SIG_DFL)
os.kill(os.getpid(), signum)
for _nm, s in catchable:
try:
signal.signal(s, _handler)
except (OSError, ValueError):
pass
# Phase 10 (Track D.3) stub helpers. When the verifier spawned a SqlStub it
# publishes the queries-log path through NYX_SQL_LOG; a sink call site that
# wants the host-side stub to see its query appends one record-per-call. The
# helper is a no-op when NYX_SQL_LOG is unset so the same fixture source still
# runs under harness modes that didn't spawn a stub.
def __nyx_stub_sql_record(query, **detail):
import os
p = os.environ.get("NYX_SQL_LOG")
if not p:
return
try:
with open(p, "a") as _f:
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write(str(query))
if not str(query).endswith('\n'):
_f.write('\n')
except OSError:
pass
# Phase 10 (Track D.3) HTTP recording helper. When the verifier spawned an
# HttpStub it publishes the side-channel log path through NYX_HTTP_LOG; a
# sink call site whose outbound request never reaches the on-the-wire
# listener (DNS-mocked, network-isolated sandbox, pre-flight check) can
# call this helper to surface the attempted call. Format matches the SQL
# helper so the host-side merger parses both streams identically.
def __nyx_stub_http_record(method, url, body=None, **detail):
import os
p = os.environ.get("NYX_HTTP_LOG")
if not p:
return
try:
with open(p, "a") as _f:
_f.write('# method: %s\n' % str(method))
_f.write('# url: %s\n' % str(url))
if body is not None:
_f.write('# body: %s\n' % str(body))
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write('%s %s\n' % (str(method), str(url)))
except OSError:
pass
_NYX_SINK_FILE = "<TMPDIR>/<ENTRY_FILE>"
_NYX_SINK_LINE = 14
_NYX_SINK_HIT = False
def _nyx_tracer(frame, event, arg):
global _NYX_SINK_HIT
if not _NYX_SINK_HIT and event == "line":
fname = frame.f_code.co_filename
if fname == _NYX_SINK_FILE or fname.endswith(_NYX_SINK_FILE) or (
os.path.basename(fname) == os.path.basename(_NYX_SINK_FILE)
):
if _NYX_SINK_LINE <= frame.f_lineno <= _NYX_SINK_LINE + 5:
_NYX_SINK_HIT = True
print("__NYX_SINK_HIT__", flush=True)
return _nyx_tracer
sys.settrace(_nyx_tracer)
# ── Payload loading ────────────────────────────────────────────────────────────
_payload_raw = os.environb.get(b"NYX_PAYLOAD", b"")
if not _payload_raw:
import base64
_payload_b64 = os.environ.get("NYX_PAYLOAD_B64", "")
if _payload_b64:
_payload_raw = base64.b64decode(_payload_b64)
try:
payload = _payload_raw.decode("utf-8")
except UnicodeDecodeError:
payload = _payload_raw.decode("latin-1")
# ── Entry module import ────────────────────────────────────────────────────────
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ".")
try:
import vuln as _entry_mod
except ImportError as _e:
print(f"NYX_IMPORT_ERROR: {_e}", file=sys.stderr, flush=True)
sys.exit(77)
# Shape: CLI entry — drives `if __name__ == "__main__":` semantics.
_argv_payload_slot = 0
_new_argv = ["vuln"]
for _i in range(_argv_payload_slot):
_new_argv.append("")
_new_argv.append(payload)
sys.argv = _new_argv
try:
# If module exposes an explicit `main` callable, prefer that.
_entry_callable = getattr(_entry_mod, "main", None)
if callable(_entry_callable):
_result = _entry_callable()
if _result is not None:
print(str(_result), flush=True)
else:
# Fall back to re-importing under `__main__` to fire the
# `if __name__ == "__main__":` block.
import runpy
runpy.run_module("vuln", run_name="__main__")
except SystemExit as _e:
sys.exit(_e.code)
except Exception as _e:
print(f"NYX_EXCEPTION: {type(_e).__name__}: {_e}", file=sys.stderr, flush=True)
sys.settrace(None)

View file

@ -0,0 +1,12 @@
"""Command injection — adversarial collision fixture.
Prints NYX_PWN_791_CMDI unconditionally without going through a shell sink.
Expected verdict: Inconclusive(OracleCollisionSuspected).
"""
def process_input(user_input):
"""Prints oracle marker without using a command execution sink."""
# Coincidental oracle match — not a shell sink.
print("NYX_PWN_791_CMDI")
return user_input.upper()

View file

@ -0,0 +1,5 @@
{
"status": "Inconclusive",
"inconclusive_reason": "OracleCollisionSuspected",
"triggered": false
}

View file

@ -0,0 +1,22 @@
"""Command injection — negative fixture.
Safe function: uses subprocess list form (no shell=True), preventing injection.
Expected verdict: NotConfirmed.
"""
import subprocess
def run_ping(host):
"""Safe: list-form subprocess, no shell expansion."""
# Sanitize: only allow alphanumeric and dots
safe_host = "".join(c for c in host if c.isalnum() or c == ".")
if not safe_host:
print("Invalid host")
return
result = subprocess.run(
["ping", "-c", "1", safe_host],
capture_output=True,
text=True,
timeout=5,
)
print(result.stdout)

View file

@ -0,0 +1,4 @@
{
"status": "NotConfirmed",
"triggered": false
}

View file

@ -0,0 +1,19 @@
"""Command injection — positive fixture.
Vulnerable function: passes user input directly to shell=True subprocess.
Expected verdict: Confirmed (payload "; echo NYX_PWN_CMDI" echoes the marker).
"""
import subprocess
def run_ping(host):
"""Vulnerable: user input in shell command."""
result = subprocess.run(
"ping -c 1 " + host,
shell=True,
capture_output=True,
text=True,
timeout=5,
)
print(result.stdout)
print(result.stderr, end="")

View file

@ -0,0 +1,4 @@
{
"status": "Confirmed",
"triggered": true
}

View file

@ -0,0 +1,11 @@
"""Command injection — unsupported fixture.
Low-confidence finding that produces Unsupported(ConfidenceTooLow).
Expected verdict: Unsupported(ConfidenceTooLow)
"""
import subprocess
def process_request(cmd):
"""Vulnerable function used in unsupported-confidence test."""
subprocess.run(cmd, shell=True)

View file

@ -0,0 +1,5 @@
{
"status": "Unsupported",
"reason": "ConfidenceTooLow",
"triggered": false
}

View file

@ -0,0 +1,21 @@
"""Phase 12 — Django view, benign."""
import re
import subprocess
from django.http import HttpResponse
_VALID_HOST = re.compile(r"^[A-Za-z0-9.-]{1,253}$")
def ping(request):
host = request.GET.get("host", "")
if not _VALID_HOST.fullmatch(host):
return HttpResponse("invalid host")
result = subprocess.run(
["ping", "-c", "1", host],
shell=False,
capture_output=True,
text=True,
timeout=5,
)
return HttpResponse(result.stdout + result.stderr)

View file

@ -0,0 +1,22 @@
"""Phase 12 — Django view, vulnerable.
Function-based view driven via `django.test.RequestFactory`. The
harness configures a minimal Django settings module at runtime so the
view can be called without a project layout.
"""
import subprocess
from django.http import HttpResponse
def ping(request):
"""Vulnerable: query parameter flows to subprocess(shell=True)."""
host = request.GET.get("host", "")
result = subprocess.run(
"ping -c 1 " + host,
shell=True,
capture_output=True,
text=True,
timeout=5,
)
return HttpResponse(result.stdout + result.stderr)

View file

@ -0,0 +1,271 @@
#!/usr/bin/env python3
"""Nyx dynamic harness — auto-generated, do not edit."""
import os
import sys
import traceback
# ── Sink-reachability probe (sys.settrace) ────────────────────────────────────
# ── __nyx_probe shim (Phase 06 — Track C.1, Phase 08 — Track C.4 + C.5) ──────
# Deny-substring list mirrors crate::dynamic::policy::DENY_KEY_SUBSTRINGS; keep
# in sync when the host-side policy gains new entries.
_NYX_DENY_SUBSTRINGS = (
"TOKEN", "SECRET", "PASSWORD", "PASSWD", "API_KEY", "APIKEY",
"PRIVATE_KEY", "CREDENTIAL", "SESSION", "COOKIE", "AUTH", "BEARER",
"AWS_ACCESS", "AWS_SESSION", "GH_TOKEN", "GITHUB_TOKEN", "NPM_TOKEN",
"PYPI_TOKEN", "DOCKER_PASS",
)
_NYX_PAYLOAD_LIMIT = 16 * 1024
_NYX_REDACTED = "<redacted-by-nyx-policy>"
def __nyx_scrub_env():
import os
out = {}
for k, v in os.environ.items():
ku = str(k).upper()
if any(n in ku for n in _NYX_DENY_SUBSTRINGS):
out[k] = _NYX_REDACTED
else:
out[k] = v
return out
def __nyx_witness(sink_callee, args):
import os
payload = os.environ.get("NYX_PAYLOAD", "")
payload_bytes = payload.encode("utf-8", "replace") if isinstance(payload, str) else bytes(payload)
if len(payload_bytes) > _NYX_PAYLOAD_LIMIT:
payload_bytes = payload_bytes[:_NYX_PAYLOAD_LIMIT]
args_repr = []
for a in args:
if isinstance(a, (bytes, bytearray)):
args_repr.append("<bytes:%d>" % len(a))
else:
args_repr.append(str(a))
try:
cwd = os.getcwd()
except OSError:
cwd = ""
return {
"env_snapshot": __nyx_scrub_env(),
"cwd": cwd,
"payload_bytes": list(payload_bytes),
"callee": str(sink_callee),
"args_repr": args_repr,
}
def __nyx_emit(rec):
import os, json
p = os.environ.get("NYX_PROBE_PATH")
if not p:
return
try:
with open(p, "a") as _f:
_f.write(json.dumps(rec) + "\n")
except OSError:
pass
def __nyx_probe(sink_callee, *args):
import os, time
serialised = []
for a in args:
if isinstance(a, (bytes, bytearray)):
serialised.append({"kind": "Bytes", "value": list(a)})
elif isinstance(a, bool):
serialised.append({"kind": "Int", "value": 1 if a else 0})
elif isinstance(a, int):
serialised.append({"kind": "Int", "value": a})
else:
serialised.append({"kind": "String", "value": str(a)})
rec = {
"sink_callee": str(sink_callee),
"args": serialised,
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Normal"},
"witness": __nyx_witness(sink_callee, args),
}
__nyx_emit(rec)
# Phase 08: sink-site signal handler. Call __nyx_install_crash_guard before
# invoking the instrumented sink so a SIGSEGV / SIGABRT / etc. is captured as
# a Crash probe (with witness) before the process aborts. The shim re-raises
# the signal on the default handler after writing so process-level outcome
# observers (exit_code) still see the death.
_NYX_SIGNAL_NAMES = {}
def __nyx_install_crash_guard(sink_callee):
import signal, os, time
catchable = []
for nm in ("SIGSEGV", "SIGABRT", "SIGBUS", "SIGFPE", "SIGILL"):
s = getattr(signal, nm, None)
if s is not None:
catchable.append((nm, s))
_NYX_SIGNAL_NAMES[s] = nm
def _handler(signum, frame):
nm = _NYX_SIGNAL_NAMES.get(signum, "SIG?")
rec = {
"sink_callee": str(sink_callee),
"args": [],
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Crash", "signal": nm},
"witness": __nyx_witness(sink_callee, []),
}
__nyx_emit(rec)
# Reset to default and re-raise so the process actually dies.
signal.signal(signum, signal.SIG_DFL)
os.kill(os.getpid(), signum)
for _nm, s in catchable:
try:
signal.signal(s, _handler)
except (OSError, ValueError):
pass
# Phase 10 (Track D.3) stub helpers. When the verifier spawned a SqlStub it
# publishes the queries-log path through NYX_SQL_LOG; a sink call site that
# wants the host-side stub to see its query appends one record-per-call. The
# helper is a no-op when NYX_SQL_LOG is unset so the same fixture source still
# runs under harness modes that didn't spawn a stub.
def __nyx_stub_sql_record(query, **detail):
import os
p = os.environ.get("NYX_SQL_LOG")
if not p:
return
try:
with open(p, "a") as _f:
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write(str(query))
if not str(query).endswith('\n'):
_f.write('\n')
except OSError:
pass
# Phase 10 (Track D.3) HTTP recording helper. When the verifier spawned an
# HttpStub it publishes the side-channel log path through NYX_HTTP_LOG; a
# sink call site whose outbound request never reaches the on-the-wire
# listener (DNS-mocked, network-isolated sandbox, pre-flight check) can
# call this helper to surface the attempted call. Format matches the SQL
# helper so the host-side merger parses both streams identically.
def __nyx_stub_http_record(method, url, body=None, **detail):
import os
p = os.environ.get("NYX_HTTP_LOG")
if not p:
return
try:
with open(p, "a") as _f:
_f.write('# method: %s\n' % str(method))
_f.write('# url: %s\n' % str(url))
if body is not None:
_f.write('# body: %s\n' % str(body))
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write('%s %s\n' % (str(method), str(url)))
except OSError:
pass
_NYX_SINK_FILE = "<TMPDIR>/<ENTRY_FILE>"
_NYX_SINK_LINE = 15
_NYX_SINK_HIT = False
def _nyx_tracer(frame, event, arg):
global _NYX_SINK_HIT
if not _NYX_SINK_HIT and event == "line":
fname = frame.f_code.co_filename
if fname == _NYX_SINK_FILE or fname.endswith(_NYX_SINK_FILE) or (
os.path.basename(fname) == os.path.basename(_NYX_SINK_FILE)
):
if _NYX_SINK_LINE <= frame.f_lineno <= _NYX_SINK_LINE + 5:
_NYX_SINK_HIT = True
print("__NYX_SINK_HIT__", flush=True)
return _nyx_tracer
sys.settrace(_nyx_tracer)
# ── Payload loading ────────────────────────────────────────────────────────────
_payload_raw = os.environb.get(b"NYX_PAYLOAD", b"")
if not _payload_raw:
import base64
_payload_b64 = os.environ.get("NYX_PAYLOAD_B64", "")
if _payload_b64:
_payload_raw = base64.b64decode(_payload_b64)
try:
payload = _payload_raw.decode("utf-8")
except UnicodeDecodeError:
payload = _payload_raw.decode("latin-1")
# ── Entry module import ────────────────────────────────────────────────────────
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ".")
try:
import vuln as _entry_mod
except ImportError as _e:
print(f"NYX_IMPORT_ERROR: {_e}", file=sys.stderr, flush=True)
sys.exit(77)
# Shape: Django view — drive via RequestFactory.
def _nyx_django_setup():
import django
from django.conf import settings
if not settings.configured:
settings.configure(
DEBUG=False,
DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
INSTALLED_APPS=["django.contrib.contenttypes", "django.contrib.auth"],
ROOT_URLCONF=None,
ALLOWED_HOSTS=["*"],
SECRET_KEY="nyx-test-key",
USE_TZ=True,
)
django.setup()
_nyx_django_setup()
from django.test import RequestFactory
_view = getattr(_entry_mod, "ping", None)
if _view is None:
# Try class-based view dispatch: find a class whose lowercased name
# matches "ping", instantiate it, and call as_view().
for attr in dir(_entry_mod):
val = getattr(_entry_mod, attr, None)
if isinstance(val, type):
try:
_view = val.as_view()
break
except Exception:
pass
if _view is None:
print("NYX_DJANGO_VIEW_NOT_FOUND", file=sys.stderr, flush=True)
sys.exit(78)
_factory = RequestFactory()
_path = "/"
_method = "GET"
_query = {}
_data = None
if "query" == "query":
_query["host"] = payload
elif "query" == "body":
_data = payload
elif "query" == "env":
os.environ["host"] = payload
_factory_method = getattr(_factory, _method.lower(), _factory.get)
_request = _factory_method(_path, data=_query or _data, content_type="text/plain" if _data else None)
try:
_resp = _view(_request)
try:
if hasattr(_resp, "render") and not getattr(_resp, "is_rendered", True):
_resp.render()
_content = getattr(_resp, "content", b"")
if isinstance(_content, (bytes, bytearray)):
_content = _content.decode("utf-8", "replace")
print(_content, flush=True)
except Exception:
pass
except SystemExit as _e:
sys.exit(_e.code)
except Exception as _e:
print(f"NYX_EXCEPTION: {type(_e).__name__}: {_e}", file=sys.stderr, flush=True)
sys.settrace(None)

View file

@ -0,0 +1,23 @@
"""Phase 12 — FastAPI route, benign."""
import re
import subprocess
from fastapi import FastAPI
app = FastAPI()
_VALID_HOST = re.compile(r"^[A-Za-z0-9.-]{1,253}$")
@app.get("/ping")
def ping(host: str = ""):
if not _VALID_HOST.fullmatch(host):
return "invalid host"
result = subprocess.run(
["ping", "-c", "1", host],
shell=False,
capture_output=True,
text=True,
timeout=5,
)
return result.stdout + result.stderr

View file

@ -0,0 +1,23 @@
"""Phase 12 — FastAPI route, vulnerable.
Nyx harness drives the route through `starlette.testclient.TestClient`
so the framework's normal request pipeline fires without a real socket.
"""
import subprocess
from fastapi import FastAPI
app = FastAPI()
@app.get("/ping")
def ping(host: str = ""):
"""Vulnerable: query parameter flows to subprocess(shell=True)."""
result = subprocess.run(
"ping -c 1 " + host,
shell=True,
capture_output=True,
text=True,
timeout=5,
)
return result.stdout + result.stderr

View file

@ -0,0 +1,277 @@
#!/usr/bin/env python3
"""Nyx dynamic harness — auto-generated, do not edit."""
import os
import sys
import traceback
# ── Sink-reachability probe (sys.settrace) ────────────────────────────────────
# ── __nyx_probe shim (Phase 06 — Track C.1, Phase 08 — Track C.4 + C.5) ──────
# Deny-substring list mirrors crate::dynamic::policy::DENY_KEY_SUBSTRINGS; keep
# in sync when the host-side policy gains new entries.
_NYX_DENY_SUBSTRINGS = (
"TOKEN", "SECRET", "PASSWORD", "PASSWD", "API_KEY", "APIKEY",
"PRIVATE_KEY", "CREDENTIAL", "SESSION", "COOKIE", "AUTH", "BEARER",
"AWS_ACCESS", "AWS_SESSION", "GH_TOKEN", "GITHUB_TOKEN", "NPM_TOKEN",
"PYPI_TOKEN", "DOCKER_PASS",
)
_NYX_PAYLOAD_LIMIT = 16 * 1024
_NYX_REDACTED = "<redacted-by-nyx-policy>"
def __nyx_scrub_env():
import os
out = {}
for k, v in os.environ.items():
ku = str(k).upper()
if any(n in ku for n in _NYX_DENY_SUBSTRINGS):
out[k] = _NYX_REDACTED
else:
out[k] = v
return out
def __nyx_witness(sink_callee, args):
import os
payload = os.environ.get("NYX_PAYLOAD", "")
payload_bytes = payload.encode("utf-8", "replace") if isinstance(payload, str) else bytes(payload)
if len(payload_bytes) > _NYX_PAYLOAD_LIMIT:
payload_bytes = payload_bytes[:_NYX_PAYLOAD_LIMIT]
args_repr = []
for a in args:
if isinstance(a, (bytes, bytearray)):
args_repr.append("<bytes:%d>" % len(a))
else:
args_repr.append(str(a))
try:
cwd = os.getcwd()
except OSError:
cwd = ""
return {
"env_snapshot": __nyx_scrub_env(),
"cwd": cwd,
"payload_bytes": list(payload_bytes),
"callee": str(sink_callee),
"args_repr": args_repr,
}
def __nyx_emit(rec):
import os, json
p = os.environ.get("NYX_PROBE_PATH")
if not p:
return
try:
with open(p, "a") as _f:
_f.write(json.dumps(rec) + "\n")
except OSError:
pass
def __nyx_probe(sink_callee, *args):
import os, time
serialised = []
for a in args:
if isinstance(a, (bytes, bytearray)):
serialised.append({"kind": "Bytes", "value": list(a)})
elif isinstance(a, bool):
serialised.append({"kind": "Int", "value": 1 if a else 0})
elif isinstance(a, int):
serialised.append({"kind": "Int", "value": a})
else:
serialised.append({"kind": "String", "value": str(a)})
rec = {
"sink_callee": str(sink_callee),
"args": serialised,
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Normal"},
"witness": __nyx_witness(sink_callee, args),
}
__nyx_emit(rec)
# Phase 08: sink-site signal handler. Call __nyx_install_crash_guard before
# invoking the instrumented sink so a SIGSEGV / SIGABRT / etc. is captured as
# a Crash probe (with witness) before the process aborts. The shim re-raises
# the signal on the default handler after writing so process-level outcome
# observers (exit_code) still see the death.
_NYX_SIGNAL_NAMES = {}
def __nyx_install_crash_guard(sink_callee):
import signal, os, time
catchable = []
for nm in ("SIGSEGV", "SIGABRT", "SIGBUS", "SIGFPE", "SIGILL"):
s = getattr(signal, nm, None)
if s is not None:
catchable.append((nm, s))
_NYX_SIGNAL_NAMES[s] = nm
def _handler(signum, frame):
nm = _NYX_SIGNAL_NAMES.get(signum, "SIG?")
rec = {
"sink_callee": str(sink_callee),
"args": [],
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Crash", "signal": nm},
"witness": __nyx_witness(sink_callee, []),
}
__nyx_emit(rec)
# Reset to default and re-raise so the process actually dies.
signal.signal(signum, signal.SIG_DFL)
os.kill(os.getpid(), signum)
for _nm, s in catchable:
try:
signal.signal(s, _handler)
except (OSError, ValueError):
pass
# Phase 10 (Track D.3) stub helpers. When the verifier spawned a SqlStub it
# publishes the queries-log path through NYX_SQL_LOG; a sink call site that
# wants the host-side stub to see its query appends one record-per-call. The
# helper is a no-op when NYX_SQL_LOG is unset so the same fixture source still
# runs under harness modes that didn't spawn a stub.
def __nyx_stub_sql_record(query, **detail):
import os
p = os.environ.get("NYX_SQL_LOG")
if not p:
return
try:
with open(p, "a") as _f:
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write(str(query))
if not str(query).endswith('\n'):
_f.write('\n')
except OSError:
pass
# Phase 10 (Track D.3) HTTP recording helper. When the verifier spawned an
# HttpStub it publishes the side-channel log path through NYX_HTTP_LOG; a
# sink call site whose outbound request never reaches the on-the-wire
# listener (DNS-mocked, network-isolated sandbox, pre-flight check) can
# call this helper to surface the attempted call. Format matches the SQL
# helper so the host-side merger parses both streams identically.
def __nyx_stub_http_record(method, url, body=None, **detail):
import os
p = os.environ.get("NYX_HTTP_LOG")
if not p:
return
try:
with open(p, "a") as _f:
_f.write('# method: %s\n' % str(method))
_f.write('# url: %s\n' % str(url))
if body is not None:
_f.write('# body: %s\n' % str(body))
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write('%s %s\n' % (str(method), str(url)))
except OSError:
pass
_NYX_SINK_FILE = "<TMPDIR>/<ENTRY_FILE>"
_NYX_SINK_LINE = 16
_NYX_SINK_HIT = False
def _nyx_tracer(frame, event, arg):
global _NYX_SINK_HIT
if not _NYX_SINK_HIT and event == "line":
fname = frame.f_code.co_filename
if fname == _NYX_SINK_FILE or fname.endswith(_NYX_SINK_FILE) or (
os.path.basename(fname) == os.path.basename(_NYX_SINK_FILE)
):
if _NYX_SINK_LINE <= frame.f_lineno <= _NYX_SINK_LINE + 5:
_NYX_SINK_HIT = True
print("__NYX_SINK_HIT__", flush=True)
return _nyx_tracer
sys.settrace(_nyx_tracer)
# ── Payload loading ────────────────────────────────────────────────────────────
_payload_raw = os.environb.get(b"NYX_PAYLOAD", b"")
if not _payload_raw:
import base64
_payload_b64 = os.environ.get("NYX_PAYLOAD_B64", "")
if _payload_b64:
_payload_raw = base64.b64decode(_payload_b64)
try:
payload = _payload_raw.decode("utf-8")
except UnicodeDecodeError:
payload = _payload_raw.decode("latin-1")
# ── Entry module import ────────────────────────────────────────────────────────
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ".")
try:
import vuln as _entry_mod
except ImportError as _e:
print(f"NYX_IMPORT_ERROR: {_e}", file=sys.stderr, flush=True)
sys.exit(77)
# Shape: FastAPI route — dispatch via starlette.testclient.TestClient.
def _nyx_resolve_fastapi_app(mod):
try:
from fastapi import FastAPI
except ImportError:
return None
for n in ("app", "application"):
v = getattr(mod, n, None)
if isinstance(v, FastAPI):
return v
for attr in dir(mod):
val = getattr(mod, attr, None)
if isinstance(val, FastAPI):
return val
return None
_app = _nyx_resolve_fastapi_app(_entry_mod)
if _app is None:
print("NYX_FASTAPI_APP_NOT_FOUND", file=sys.stderr, flush=True)
sys.exit(78)
try:
from starlette.testclient import TestClient
except ImportError:
print("NYX_FASTAPI_TESTCLIENT_MISSING", file=sys.stderr, flush=True)
sys.exit(79)
_path = None
for _r in _app.routes:
_name = getattr(_r, "name", None)
_endpoint = getattr(_r, "endpoint", None)
_endpoint_name = getattr(_endpoint, "__name__", None)
if _name == "ping" or _endpoint_name == "ping":
_path = getattr(_r, "path", None)
break
if _path is None and _app.routes:
_path = getattr(_app.routes[0], "path", None)
if _path is None:
print("NYX_FASTAPI_ROUTE_NOT_FOUND", file=sys.stderr, flush=True)
sys.exit(80)
# Strip path parameters; replace `{param}` with the payload when used
# as the path slot, otherwise with "x".
import re
if "query" == "path":
_path = re.sub(r"\{[^}]+\}", payload, _path, count=1)
else:
_path = re.sub(r"\{[^}]+\}", "x", _path)
_client = TestClient(_app, raise_server_exceptions=False)
_method = "GET"
_query = {}
_body = None
if "query" == "query":
_query["host"] = payload
elif "query" == "body":
_body = payload
elif "query" == "env":
os.environ["host"] = payload
try:
_resp = _client.request(_method, _path, params=_query, content=_body)
try:
print(_resp.text, flush=True)
except Exception:
pass
except SystemExit as _e:
sys.exit(_e.code)
except Exception as _e:
print(f"NYX_EXCEPTION: {type(_e).__name__}: {_e}", file=sys.stderr, flush=True)
sys.settrace(None)

View file

@ -0,0 +1,12 @@
"""File I/O — adversarial collision fixture.
Prints "root:" unconditionally without reading any file.
Expected verdict: Inconclusive(OracleCollisionSuspected).
"""
def read_file(path):
"""Prints oracle marker without opening any file."""
# Coincidental match — not a file I/O sink.
print("root: nobody:*:0:0:System Administrator:/var/root:/bin/sh")
return path

View file

@ -0,0 +1,5 @@
{
"status": "Inconclusive",
"inconclusive_reason": "OracleCollisionSuspected",
"triggered": false
}

View file

@ -0,0 +1,22 @@
"""File I/O — negative fixture.
Safe function: validates path stays within allowed directory.
Expected verdict: NotConfirmed.
"""
import os
def read_file(path):
"""Safe: resolves and validates path is within /tmp/safe-uploads/."""
base_dir = "/tmp/safe-uploads"
os.makedirs(base_dir, exist_ok=True)
# Resolve to absolute path and check it stays within base_dir.
abs_path = os.path.realpath(os.path.join(base_dir, path))
if not abs_path.startswith(base_dir + os.sep) and abs_path != base_dir:
print("Access denied: path traversal detected")
return
try:
with open(abs_path) as f:
print(f.read())
except FileNotFoundError:
print("File not found")

View file

@ -0,0 +1,4 @@
{
"status": "NotConfirmed",
"triggered": false
}

View file

@ -0,0 +1,14 @@
"""File I/O — positive fixture.
Vulnerable function: opens a file at a user-controlled path.
Expected verdict: Confirmed (path traversal payload reaches /etc/passwd).
"""
def read_file(path):
"""Vulnerable: reads file at user-controlled path."""
try:
with open(path) as f:
print(f.read())
except (OSError, PermissionError) as e:
print(f"Error reading {path}: {e}", end="")

View file

@ -0,0 +1,4 @@
{
"status": "Confirmed",
"triggered": true
}

View file

@ -0,0 +1,10 @@
"""File I/O — unsupported fixture (low confidence).
Expected verdict: Unsupported(ConfidenceTooLow)
"""
def read_config(path):
"""Vulnerable function in unsupported-confidence test."""
with open(path) as f:
return f.read()

View file

@ -0,0 +1,5 @@
{
"status": "Unsupported",
"reason": "ConfidenceTooLow",
"triggered": false
}

View file

@ -0,0 +1,24 @@
"""Phase 12 — Flask route, benign."""
import re
import subprocess
from flask import Flask, request
app = Flask(__name__)
_VALID_HOST = re.compile(r"^[A-Za-z0-9.-]{1,253}$")
@app.route("/ping", methods=["GET"])
def ping():
host = request.args.get("host", "")
if not _VALID_HOST.fullmatch(host):
return "invalid host"
result = subprocess.run(
["ping", "-c", "1", host],
shell=False,
capture_output=True,
text=True,
timeout=5,
)
return result.stdout + result.stderr

View file

@ -0,0 +1,25 @@
"""Phase 12 — Flask route, vulnerable.
Vulnerable route reads the `host` query parameter and concatenates it
into a shell command. Nyx harness reaches the route via
`app.test_client()` so no real network listener is bound.
"""
import subprocess
from flask import Flask, request
app = Flask(__name__)
@app.route("/ping", methods=["GET"])
def ping():
"""Vulnerable: untrusted query param flows to subprocess(shell=True)."""
host = request.args.get("host", "")
result = subprocess.run(
"ping -c 1 " + host,
shell=True,
capture_output=True,
text=True,
timeout=5,
)
return result.stdout + result.stderr

View file

@ -0,0 +1,275 @@
#!/usr/bin/env python3
"""Nyx dynamic harness — auto-generated, do not edit."""
import os
import sys
import traceback
# ── Sink-reachability probe (sys.settrace) ────────────────────────────────────
# ── __nyx_probe shim (Phase 06 — Track C.1, Phase 08 — Track C.4 + C.5) ──────
# Deny-substring list mirrors crate::dynamic::policy::DENY_KEY_SUBSTRINGS; keep
# in sync when the host-side policy gains new entries.
_NYX_DENY_SUBSTRINGS = (
"TOKEN", "SECRET", "PASSWORD", "PASSWD", "API_KEY", "APIKEY",
"PRIVATE_KEY", "CREDENTIAL", "SESSION", "COOKIE", "AUTH", "BEARER",
"AWS_ACCESS", "AWS_SESSION", "GH_TOKEN", "GITHUB_TOKEN", "NPM_TOKEN",
"PYPI_TOKEN", "DOCKER_PASS",
)
_NYX_PAYLOAD_LIMIT = 16 * 1024
_NYX_REDACTED = "<redacted-by-nyx-policy>"
def __nyx_scrub_env():
import os
out = {}
for k, v in os.environ.items():
ku = str(k).upper()
if any(n in ku for n in _NYX_DENY_SUBSTRINGS):
out[k] = _NYX_REDACTED
else:
out[k] = v
return out
def __nyx_witness(sink_callee, args):
import os
payload = os.environ.get("NYX_PAYLOAD", "")
payload_bytes = payload.encode("utf-8", "replace") if isinstance(payload, str) else bytes(payload)
if len(payload_bytes) > _NYX_PAYLOAD_LIMIT:
payload_bytes = payload_bytes[:_NYX_PAYLOAD_LIMIT]
args_repr = []
for a in args:
if isinstance(a, (bytes, bytearray)):
args_repr.append("<bytes:%d>" % len(a))
else:
args_repr.append(str(a))
try:
cwd = os.getcwd()
except OSError:
cwd = ""
return {
"env_snapshot": __nyx_scrub_env(),
"cwd": cwd,
"payload_bytes": list(payload_bytes),
"callee": str(sink_callee),
"args_repr": args_repr,
}
def __nyx_emit(rec):
import os, json
p = os.environ.get("NYX_PROBE_PATH")
if not p:
return
try:
with open(p, "a") as _f:
_f.write(json.dumps(rec) + "\n")
except OSError:
pass
def __nyx_probe(sink_callee, *args):
import os, time
serialised = []
for a in args:
if isinstance(a, (bytes, bytearray)):
serialised.append({"kind": "Bytes", "value": list(a)})
elif isinstance(a, bool):
serialised.append({"kind": "Int", "value": 1 if a else 0})
elif isinstance(a, int):
serialised.append({"kind": "Int", "value": a})
else:
serialised.append({"kind": "String", "value": str(a)})
rec = {
"sink_callee": str(sink_callee),
"args": serialised,
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Normal"},
"witness": __nyx_witness(sink_callee, args),
}
__nyx_emit(rec)
# Phase 08: sink-site signal handler. Call __nyx_install_crash_guard before
# invoking the instrumented sink so a SIGSEGV / SIGABRT / etc. is captured as
# a Crash probe (with witness) before the process aborts. The shim re-raises
# the signal on the default handler after writing so process-level outcome
# observers (exit_code) still see the death.
_NYX_SIGNAL_NAMES = {}
def __nyx_install_crash_guard(sink_callee):
import signal, os, time
catchable = []
for nm in ("SIGSEGV", "SIGABRT", "SIGBUS", "SIGFPE", "SIGILL"):
s = getattr(signal, nm, None)
if s is not None:
catchable.append((nm, s))
_NYX_SIGNAL_NAMES[s] = nm
def _handler(signum, frame):
nm = _NYX_SIGNAL_NAMES.get(signum, "SIG?")
rec = {
"sink_callee": str(sink_callee),
"args": [],
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Crash", "signal": nm},
"witness": __nyx_witness(sink_callee, []),
}
__nyx_emit(rec)
# Reset to default and re-raise so the process actually dies.
signal.signal(signum, signal.SIG_DFL)
os.kill(os.getpid(), signum)
for _nm, s in catchable:
try:
signal.signal(s, _handler)
except (OSError, ValueError):
pass
# Phase 10 (Track D.3) stub helpers. When the verifier spawned a SqlStub it
# publishes the queries-log path through NYX_SQL_LOG; a sink call site that
# wants the host-side stub to see its query appends one record-per-call. The
# helper is a no-op when NYX_SQL_LOG is unset so the same fixture source still
# runs under harness modes that didn't spawn a stub.
def __nyx_stub_sql_record(query, **detail):
import os
p = os.environ.get("NYX_SQL_LOG")
if not p:
return
try:
with open(p, "a") as _f:
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write(str(query))
if not str(query).endswith('\n'):
_f.write('\n')
except OSError:
pass
# Phase 10 (Track D.3) HTTP recording helper. When the verifier spawned an
# HttpStub it publishes the side-channel log path through NYX_HTTP_LOG; a
# sink call site whose outbound request never reaches the on-the-wire
# listener (DNS-mocked, network-isolated sandbox, pre-flight check) can
# call this helper to surface the attempted call. Format matches the SQL
# helper so the host-side merger parses both streams identically.
def __nyx_stub_http_record(method, url, body=None, **detail):
import os
p = os.environ.get("NYX_HTTP_LOG")
if not p:
return
try:
with open(p, "a") as _f:
_f.write('# method: %s\n' % str(method))
_f.write('# url: %s\n' % str(url))
if body is not None:
_f.write('# body: %s\n' % str(body))
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write('%s %s\n' % (str(method), str(url)))
except OSError:
pass
_NYX_SINK_FILE = "<TMPDIR>/<ENTRY_FILE>"
_NYX_SINK_LINE = 18
_NYX_SINK_HIT = False
def _nyx_tracer(frame, event, arg):
global _NYX_SINK_HIT
if not _NYX_SINK_HIT and event == "line":
fname = frame.f_code.co_filename
if fname == _NYX_SINK_FILE or fname.endswith(_NYX_SINK_FILE) or (
os.path.basename(fname) == os.path.basename(_NYX_SINK_FILE)
):
if _NYX_SINK_LINE <= frame.f_lineno <= _NYX_SINK_LINE + 5:
_NYX_SINK_HIT = True
print("__NYX_SINK_HIT__", flush=True)
return _nyx_tracer
sys.settrace(_nyx_tracer)
# ── Payload loading ────────────────────────────────────────────────────────────
_payload_raw = os.environb.get(b"NYX_PAYLOAD", b"")
if not _payload_raw:
import base64
_payload_b64 = os.environ.get("NYX_PAYLOAD_B64", "")
if _payload_b64:
_payload_raw = base64.b64decode(_payload_b64)
try:
payload = _payload_raw.decode("utf-8")
except UnicodeDecodeError:
payload = _payload_raw.decode("latin-1")
# ── Entry module import ────────────────────────────────────────────────────────
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ".")
try:
import vuln as _entry_mod
except ImportError as _e:
print(f"NYX_IMPORT_ERROR: {_e}", file=sys.stderr, flush=True)
sys.exit(77)
# Shape: Flask route — dispatch via app.test_client().
def _nyx_resolve_flask_app(mod):
from flask import Flask
candidates = [getattr(mod, n, None) for n in ("app", "application", "create_app")]
for c in candidates:
if callable(c) and not isinstance(c, Flask):
try:
got = c()
if isinstance(got, Flask):
return got
except TypeError:
pass
if isinstance(c, Flask):
return c
for attr in dir(mod):
val = getattr(mod, attr, None)
if isinstance(val, Flask):
return val
return None
_app = _nyx_resolve_flask_app(_entry_mod)
if _app is None:
print("NYX_FLASK_APP_NOT_FOUND", file=sys.stderr, flush=True)
sys.exit(78)
_route = None
for _r in _app.url_map.iter_rules():
if _r.endpoint == "ping" or _r.endpoint.endswith("." + "ping"):
_route = _r
break
if _route is None:
# Fall back: any rule will do, but pick the first POST/GET.
_rules = list(_app.url_map.iter_rules())
_route = _rules[0] if _rules else None
if _route is None:
print("NYX_FLASK_ROUTE_NOT_FOUND", file=sys.stderr, flush=True)
sys.exit(79)
_path = _route.rule
# Strip route parameters; replace `<param>` with payload when used as
# the path slot, otherwise with "x".
import re
if "query" == "path":
_path = re.sub(r"<[^>]+>", payload, _path, count=1)
else:
_path = re.sub(r"<[^>]+>", "x", _path)
_client = _app.test_client()
_method = "GET"
_query = {}
_data = None
if "query" == "query":
_query["host"] = payload
elif "query" == "body":
_data = payload
elif "query" == "env":
os.environ["host"] = payload
try:
_resp = _client.open(_path, method=_method, query_string=_query, data=_data)
try:
print(_resp.get_data(as_text=True), flush=True)
except Exception:
pass
except SystemExit as _e:
sys.exit(_e.code)
except Exception as _e:
print(f"NYX_EXCEPTION: {type(_e).__name__}: {_e}", file=sys.stderr, flush=True)
sys.settrace(None)

View file

@ -0,0 +1,28 @@
"""Phase 12 — generic shape, benign.
Validates the input against a strict allow-list (alphanumerics + dots
only RFC-1035 hostname character set) and refuses to shell out when
the input contains anything outside the allow-list. The CMDI marker
substring (`NYX_PWN_CMDI`) never reaches stdout because the function
returns before any subprocess call when the validation fails.
"""
import re
import subprocess
_VALID_HOST = re.compile(r"^[A-Za-z0-9.-]{1,253}$")
def run_ping(host):
"""Safe: allow-list validation; refuse and return on mismatch."""
if not _VALID_HOST.fullmatch(host or ""):
print("invalid host")
return
result = subprocess.run(
["ping", "-c", "1", host],
shell=False,
capture_output=True,
text=True,
timeout=5,
)
print(result.stdout)
print(result.stderr, end="")

View file

@ -0,0 +1,20 @@
"""Phase 12 — generic shape, vulnerable.
Module-level function that shells out with user input directly
concatenated. Mirrors `cmdi_positive.py` but lives under the per-shape
fixture tree so the shape detector hits the `Generic` path.
"""
import subprocess
def run_ping(host):
"""Vulnerable: user input concatenated into shell command."""
result = subprocess.run(
"ping -c 1 " + host,
shell=True,
capture_output=True,
text=True,
timeout=5,
)
print(result.stdout)
print(result.stderr, end="")

View file

@ -0,0 +1,221 @@
#!/usr/bin/env python3
"""Nyx dynamic harness — auto-generated, do not edit."""
import os
import sys
import traceback
# ── Sink-reachability probe (sys.settrace) ────────────────────────────────────
# ── __nyx_probe shim (Phase 06 — Track C.1, Phase 08 — Track C.4 + C.5) ──────
# Deny-substring list mirrors crate::dynamic::policy::DENY_KEY_SUBSTRINGS; keep
# in sync when the host-side policy gains new entries.
_NYX_DENY_SUBSTRINGS = (
"TOKEN", "SECRET", "PASSWORD", "PASSWD", "API_KEY", "APIKEY",
"PRIVATE_KEY", "CREDENTIAL", "SESSION", "COOKIE", "AUTH", "BEARER",
"AWS_ACCESS", "AWS_SESSION", "GH_TOKEN", "GITHUB_TOKEN", "NPM_TOKEN",
"PYPI_TOKEN", "DOCKER_PASS",
)
_NYX_PAYLOAD_LIMIT = 16 * 1024
_NYX_REDACTED = "<redacted-by-nyx-policy>"
def __nyx_scrub_env():
import os
out = {}
for k, v in os.environ.items():
ku = str(k).upper()
if any(n in ku for n in _NYX_DENY_SUBSTRINGS):
out[k] = _NYX_REDACTED
else:
out[k] = v
return out
def __nyx_witness(sink_callee, args):
import os
payload = os.environ.get("NYX_PAYLOAD", "")
payload_bytes = payload.encode("utf-8", "replace") if isinstance(payload, str) else bytes(payload)
if len(payload_bytes) > _NYX_PAYLOAD_LIMIT:
payload_bytes = payload_bytes[:_NYX_PAYLOAD_LIMIT]
args_repr = []
for a in args:
if isinstance(a, (bytes, bytearray)):
args_repr.append("<bytes:%d>" % len(a))
else:
args_repr.append(str(a))
try:
cwd = os.getcwd()
except OSError:
cwd = ""
return {
"env_snapshot": __nyx_scrub_env(),
"cwd": cwd,
"payload_bytes": list(payload_bytes),
"callee": str(sink_callee),
"args_repr": args_repr,
}
def __nyx_emit(rec):
import os, json
p = os.environ.get("NYX_PROBE_PATH")
if not p:
return
try:
with open(p, "a") as _f:
_f.write(json.dumps(rec) + "\n")
except OSError:
pass
def __nyx_probe(sink_callee, *args):
import os, time
serialised = []
for a in args:
if isinstance(a, (bytes, bytearray)):
serialised.append({"kind": "Bytes", "value": list(a)})
elif isinstance(a, bool):
serialised.append({"kind": "Int", "value": 1 if a else 0})
elif isinstance(a, int):
serialised.append({"kind": "Int", "value": a})
else:
serialised.append({"kind": "String", "value": str(a)})
rec = {
"sink_callee": str(sink_callee),
"args": serialised,
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Normal"},
"witness": __nyx_witness(sink_callee, args),
}
__nyx_emit(rec)
# Phase 08: sink-site signal handler. Call __nyx_install_crash_guard before
# invoking the instrumented sink so a SIGSEGV / SIGABRT / etc. is captured as
# a Crash probe (with witness) before the process aborts. The shim re-raises
# the signal on the default handler after writing so process-level outcome
# observers (exit_code) still see the death.
_NYX_SIGNAL_NAMES = {}
def __nyx_install_crash_guard(sink_callee):
import signal, os, time
catchable = []
for nm in ("SIGSEGV", "SIGABRT", "SIGBUS", "SIGFPE", "SIGILL"):
s = getattr(signal, nm, None)
if s is not None:
catchable.append((nm, s))
_NYX_SIGNAL_NAMES[s] = nm
def _handler(signum, frame):
nm = _NYX_SIGNAL_NAMES.get(signum, "SIG?")
rec = {
"sink_callee": str(sink_callee),
"args": [],
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Crash", "signal": nm},
"witness": __nyx_witness(sink_callee, []),
}
__nyx_emit(rec)
# Reset to default and re-raise so the process actually dies.
signal.signal(signum, signal.SIG_DFL)
os.kill(os.getpid(), signum)
for _nm, s in catchable:
try:
signal.signal(s, _handler)
except (OSError, ValueError):
pass
# Phase 10 (Track D.3) stub helpers. When the verifier spawned a SqlStub it
# publishes the queries-log path through NYX_SQL_LOG; a sink call site that
# wants the host-side stub to see its query appends one record-per-call. The
# helper is a no-op when NYX_SQL_LOG is unset so the same fixture source still
# runs under harness modes that didn't spawn a stub.
def __nyx_stub_sql_record(query, **detail):
import os
p = os.environ.get("NYX_SQL_LOG")
if not p:
return
try:
with open(p, "a") as _f:
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write(str(query))
if not str(query).endswith('\n'):
_f.write('\n')
except OSError:
pass
# Phase 10 (Track D.3) HTTP recording helper. When the verifier spawned an
# HttpStub it publishes the side-channel log path through NYX_HTTP_LOG; a
# sink call site whose outbound request never reaches the on-the-wire
# listener (DNS-mocked, network-isolated sandbox, pre-flight check) can
# call this helper to surface the attempted call. Format matches the SQL
# helper so the host-side merger parses both streams identically.
def __nyx_stub_http_record(method, url, body=None, **detail):
import os
p = os.environ.get("NYX_HTTP_LOG")
if not p:
return
try:
with open(p, "a") as _f:
_f.write('# method: %s\n' % str(method))
_f.write('# url: %s\n' % str(url))
if body is not None:
_f.write('# body: %s\n' % str(body))
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write('%s %s\n' % (str(method), str(url)))
except OSError:
pass
_NYX_SINK_FILE = "<TMPDIR>/<ENTRY_FILE>"
_NYX_SINK_LINE = 12
_NYX_SINK_HIT = False
def _nyx_tracer(frame, event, arg):
global _NYX_SINK_HIT
if not _NYX_SINK_HIT and event == "line":
fname = frame.f_code.co_filename
if fname == _NYX_SINK_FILE or fname.endswith(_NYX_SINK_FILE) or (
os.path.basename(fname) == os.path.basename(_NYX_SINK_FILE)
):
if _NYX_SINK_LINE <= frame.f_lineno <= _NYX_SINK_LINE + 5:
_NYX_SINK_HIT = True
print("__NYX_SINK_HIT__", flush=True)
return _nyx_tracer
sys.settrace(_nyx_tracer)
# ── Payload loading ────────────────────────────────────────────────────────────
_payload_raw = os.environb.get(b"NYX_PAYLOAD", b"")
if not _payload_raw:
import base64
_payload_b64 = os.environ.get("NYX_PAYLOAD_B64", "")
if _payload_b64:
_payload_raw = base64.b64decode(_payload_b64)
try:
payload = _payload_raw.decode("utf-8")
except UnicodeDecodeError:
payload = _payload_raw.decode("latin-1")
# ── Entry module import ────────────────────────────────────────────────────────
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ".")
try:
import vuln as _entry_mod
except ImportError as _e:
print(f"NYX_IMPORT_ERROR: {_e}", file=sys.stderr, flush=True)
sys.exit(77)
# Shape: generic module-level function.
try:
_result = _entry_mod.run_ping(payload)
if _result is not None:
try:
print(str(_result), flush=True)
except Exception:
pass
except SystemExit as _e:
sys.exit(_e.code)
except Exception as _e:
print(f"NYX_EXCEPTION: {type(_e).__name__}: {_e}", file=sys.stderr, flush=True)
sys.settrace(None)

View file

@ -0,0 +1,22 @@
"""Phase 12 — pytest shape, benign."""
import os
import re
import subprocess
_VALID_HOST = re.compile(r"^[A-Za-z0-9.-]{1,253}$")
def test_run_ping():
host = os.environ.get("NYX_PAYLOAD", "")
if not _VALID_HOST.fullmatch(host):
print("invalid host")
return
result = subprocess.run(
["ping", "-c", "1", host],
shell=False,
capture_output=True,
text=True,
timeout=5,
)
print(result.stdout)
print(result.stderr, end="")

View file

@ -0,0 +1,22 @@
"""Phase 12 — pytest shape, vulnerable.
Pytest convention: function name starts with `test_`. Nyx harness
injects the payload via the `NYX_PAYLOAD` env var (the same channel
pytest fixtures typically read from).
"""
import os
import subprocess
def test_run_ping():
"""Vulnerable test: reads host from env, concatenates into shell."""
host = os.environ.get("NYX_PAYLOAD", "")
result = subprocess.run(
"ping -c 1 " + host,
shell=True,
capture_output=True,
text=True,
timeout=5,
)
print(result.stdout)
print(result.stderr, end="")

View file

@ -0,0 +1,224 @@
#!/usr/bin/env python3
"""Nyx dynamic harness — auto-generated, do not edit."""
import os
import sys
import traceback
# ── Sink-reachability probe (sys.settrace) ────────────────────────────────────
# ── __nyx_probe shim (Phase 06 — Track C.1, Phase 08 — Track C.4 + C.5) ──────
# Deny-substring list mirrors crate::dynamic::policy::DENY_KEY_SUBSTRINGS; keep
# in sync when the host-side policy gains new entries.
_NYX_DENY_SUBSTRINGS = (
"TOKEN", "SECRET", "PASSWORD", "PASSWD", "API_KEY", "APIKEY",
"PRIVATE_KEY", "CREDENTIAL", "SESSION", "COOKIE", "AUTH", "BEARER",
"AWS_ACCESS", "AWS_SESSION", "GH_TOKEN", "GITHUB_TOKEN", "NPM_TOKEN",
"PYPI_TOKEN", "DOCKER_PASS",
)
_NYX_PAYLOAD_LIMIT = 16 * 1024
_NYX_REDACTED = "<redacted-by-nyx-policy>"
def __nyx_scrub_env():
import os
out = {}
for k, v in os.environ.items():
ku = str(k).upper()
if any(n in ku for n in _NYX_DENY_SUBSTRINGS):
out[k] = _NYX_REDACTED
else:
out[k] = v
return out
def __nyx_witness(sink_callee, args):
import os
payload = os.environ.get("NYX_PAYLOAD", "")
payload_bytes = payload.encode("utf-8", "replace") if isinstance(payload, str) else bytes(payload)
if len(payload_bytes) > _NYX_PAYLOAD_LIMIT:
payload_bytes = payload_bytes[:_NYX_PAYLOAD_LIMIT]
args_repr = []
for a in args:
if isinstance(a, (bytes, bytearray)):
args_repr.append("<bytes:%d>" % len(a))
else:
args_repr.append(str(a))
try:
cwd = os.getcwd()
except OSError:
cwd = ""
return {
"env_snapshot": __nyx_scrub_env(),
"cwd": cwd,
"payload_bytes": list(payload_bytes),
"callee": str(sink_callee),
"args_repr": args_repr,
}
def __nyx_emit(rec):
import os, json
p = os.environ.get("NYX_PROBE_PATH")
if not p:
return
try:
with open(p, "a") as _f:
_f.write(json.dumps(rec) + "\n")
except OSError:
pass
def __nyx_probe(sink_callee, *args):
import os, time
serialised = []
for a in args:
if isinstance(a, (bytes, bytearray)):
serialised.append({"kind": "Bytes", "value": list(a)})
elif isinstance(a, bool):
serialised.append({"kind": "Int", "value": 1 if a else 0})
elif isinstance(a, int):
serialised.append({"kind": "Int", "value": a})
else:
serialised.append({"kind": "String", "value": str(a)})
rec = {
"sink_callee": str(sink_callee),
"args": serialised,
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Normal"},
"witness": __nyx_witness(sink_callee, args),
}
__nyx_emit(rec)
# Phase 08: sink-site signal handler. Call __nyx_install_crash_guard before
# invoking the instrumented sink so a SIGSEGV / SIGABRT / etc. is captured as
# a Crash probe (with witness) before the process aborts. The shim re-raises
# the signal on the default handler after writing so process-level outcome
# observers (exit_code) still see the death.
_NYX_SIGNAL_NAMES = {}
def __nyx_install_crash_guard(sink_callee):
import signal, os, time
catchable = []
for nm in ("SIGSEGV", "SIGABRT", "SIGBUS", "SIGFPE", "SIGILL"):
s = getattr(signal, nm, None)
if s is not None:
catchable.append((nm, s))
_NYX_SIGNAL_NAMES[s] = nm
def _handler(signum, frame):
nm = _NYX_SIGNAL_NAMES.get(signum, "SIG?")
rec = {
"sink_callee": str(sink_callee),
"args": [],
"captured_at_ns": time.time_ns(),
"payload_id": os.environ.get("NYX_PAYLOAD_ID", ""),
"kind": {"kind": "Crash", "signal": nm},
"witness": __nyx_witness(sink_callee, []),
}
__nyx_emit(rec)
# Reset to default and re-raise so the process actually dies.
signal.signal(signum, signal.SIG_DFL)
os.kill(os.getpid(), signum)
for _nm, s in catchable:
try:
signal.signal(s, _handler)
except (OSError, ValueError):
pass
# Phase 10 (Track D.3) stub helpers. When the verifier spawned a SqlStub it
# publishes the queries-log path through NYX_SQL_LOG; a sink call site that
# wants the host-side stub to see its query appends one record-per-call. The
# helper is a no-op when NYX_SQL_LOG is unset so the same fixture source still
# runs under harness modes that didn't spawn a stub.
def __nyx_stub_sql_record(query, **detail):
import os
p = os.environ.get("NYX_SQL_LOG")
if not p:
return
try:
with open(p, "a") as _f:
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write(str(query))
if not str(query).endswith('\n'):
_f.write('\n')
except OSError:
pass
# Phase 10 (Track D.3) HTTP recording helper. When the verifier spawned an
# HttpStub it publishes the side-channel log path through NYX_HTTP_LOG; a
# sink call site whose outbound request never reaches the on-the-wire
# listener (DNS-mocked, network-isolated sandbox, pre-flight check) can
# call this helper to surface the attempted call. Format matches the SQL
# helper so the host-side merger parses both streams identically.
def __nyx_stub_http_record(method, url, body=None, **detail):
import os
p = os.environ.get("NYX_HTTP_LOG")
if not p:
return
try:
with open(p, "a") as _f:
_f.write('# method: %s\n' % str(method))
_f.write('# url: %s\n' % str(url))
if body is not None:
_f.write('# body: %s\n' % str(body))
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write('%s %s\n' % (str(method), str(url)))
except OSError:
pass
_NYX_SINK_FILE = "<TMPDIR>/<ENTRY_FILE>"
_NYX_SINK_LINE = 14
_NYX_SINK_HIT = False
def _nyx_tracer(frame, event, arg):
global _NYX_SINK_HIT
if not _NYX_SINK_HIT and event == "line":
fname = frame.f_code.co_filename
if fname == _NYX_SINK_FILE or fname.endswith(_NYX_SINK_FILE) or (
os.path.basename(fname) == os.path.basename(_NYX_SINK_FILE)
):
if _NYX_SINK_LINE <= frame.f_lineno <= _NYX_SINK_LINE + 5:
_NYX_SINK_HIT = True
print("__NYX_SINK_HIT__", flush=True)
return _nyx_tracer
sys.settrace(_nyx_tracer)
# ── Payload loading ────────────────────────────────────────────────────────────
_payload_raw = os.environb.get(b"NYX_PAYLOAD", b"")
if not _payload_raw:
import base64
_payload_b64 = os.environ.get("NYX_PAYLOAD_B64", "")
if _payload_b64:
_payload_raw = base64.b64decode(_payload_b64)
try:
payload = _payload_raw.decode("utf-8")
except UnicodeDecodeError:
payload = _payload_raw.decode("latin-1")
# ── Entry module import ────────────────────────────────────────────────────────
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ".")
try:
import vuln as _entry_mod
except ImportError as _e:
print(f"NYX_IMPORT_ERROR: {_e}", file=sys.stderr, flush=True)
sys.exit(77)
# Shape: pytest function — drive the single test directly.
os.environ["NYX_PAYLOAD"] = payload
try:
_result = _entry_mod.test_run_ping()
if _result is not None:
try:
print(str(_result), flush=True)
except Exception:
pass
except AssertionError as _e:
# AssertionError is the typical pytest failure path; observable.
print(f"NYX_ASSERT: {_e}", file=sys.stderr, flush=True)
except SystemExit as _e:
sys.exit(_e.code)
except Exception as _e:
print(f"NYX_EXCEPTION: {type(_e).__name__}: {_e}", file=sys.stderr, flush=True)
sys.settrace(None)

View file

@ -0,0 +1,19 @@
"""SQL injection — adversarial collision fixture.
This function prints "NYX_SQL_CONFIRMED" unconditionally (simulating a
coincidental oracle match). The sink is a harmless print statement, not
an actual SQL execution.
Expected verdict: Inconclusive(OracleCollisionSuspected)
- oracle_fired = True (OutputContains("NYX_SQL_CONFIRMED"))
- sink_hit = False (sys.settrace probe never fires on SQL execute line)
"""
def get_value(user_input):
"""Always prints the oracle marker — no actual SQL involved."""
# Coincidental output match — not a real vulnerability.
print("NYX_SQL_CONFIRMED")
# The above is not a SQL sink; the settrace probe on a real sink line
# (different line number or file) will not fire.
return user_input

View file

@ -0,0 +1,5 @@
{
"status": "Inconclusive",
"inconclusive_reason": "OracleCollisionSuspected",
"triggered": false
}

View file

@ -0,0 +1,18 @@
"""SQL injection — negative fixture.
Safe function: uses parameterized queries.
Expected verdict: NotConfirmed (parameterized query prevents injection).
"""
import sqlite3
def login(username):
"""Safe login: parameterized query prevents SQL injection."""
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (id INTEGER, name TEXT)")
conn.execute("INSERT INTO users VALUES (1, 'alice')")
# Safe: parameterized query
rows = conn.execute("SELECT name FROM users WHERE name=?", (username,)).fetchall()
for row in rows:
print(row[0])
conn.close()

View file

@ -0,0 +1,4 @@
{
"status": "NotConfirmed",
"triggered": false
}

View file

@ -0,0 +1,27 @@
"""SQL injection — positive fixture.
Vulnerable function: directly concatenates user input into SQL.
Expected verdict: Confirmed (SQLI corpus UNION payload causes "NYX_SQL_CONFIRMED"
to appear in output when the fixture prints query results).
"""
import sqlite3
def login(username):
"""Vulnerable login: direct string concatenation in SQL query."""
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (id INTEGER, name TEXT)")
conn.execute("INSERT INTO users VALUES (1, 'alice')")
conn.execute("INSERT INTO users VALUES (2, 'bob')")
# Vulnerable: user input directly concatenated
query = "SELECT name FROM users WHERE name='" + username + "'"
try:
rows = conn.execute(query).fetchall()
for row in rows:
print(row[0])
except sqlite3.OperationalError as e:
# Error-based: print query on failure (common in debug mode)
print(f"DB query: {query}")
print(f"DB error: {e}", end="")
finally:
conn.close()

View file

@ -0,0 +1,4 @@
{
"status": "Confirmed",
"triggered": true
}

View file

@ -0,0 +1,18 @@
"""SQL injection — unsupported fixture.
This file contains a vulnerable class method. The test creates a Diag
with `confidence = Low`, which makes `from_finding` return
`Err(UnsupportedReason::ConfidenceTooLow)`.
Expected verdict: Unsupported(ConfidenceTooLow)
"""
import sqlite3
class UserRepository:
"""Vulnerable class method — entry kind unsupported in current milestone."""
def find_user(self, name):
conn = sqlite3.connect(":memory:")
query = "SELECT * FROM users WHERE name='" + name + "'"
return conn.execute(query).fetchall()

View file

@ -0,0 +1,5 @@
{
"status": "Unsupported",
"reason": "ConfidenceTooLow",
"triggered": false
}

View file

@ -0,0 +1,28 @@
"""SQL injection fixture with a secrets file in the project.
Used for the secret-bearing fixture test: verifies that the AWS key
from .env does not appear in cache, telemetry, or repro outcome.json
after redaction.
Expected verdict: result depends on execution; secret must never appear
in any output artifact.
"""
import sqlite3
import os
def login(username):
"""Vulnerable login — same as sqli_positive but in a project with .env."""
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (id INTEGER, name TEXT)")
conn.execute("INSERT INTO users VALUES (1, 'alice')")
query = "SELECT name FROM users WHERE name='" + username + "'"
try:
rows = conn.execute(query).fetchall()
for row in rows:
print(row[0])
except sqlite3.OperationalError as e:
print(f"DB query: {query}")
print(f"DB error: {e}", end="")
finally:
conn.close()

View file

@ -0,0 +1,11 @@
"""SSRF — adversarial collision fixture.
Prints "daemon:" unconditionally without making any network request.
Expected verdict: Inconclusive(OracleCollisionSuspected).
"""
def fetch_url(url):
"""Prints oracle marker without fetching any URL."""
print("daemon:*:1:1:System Services:/var/root:/usr/bin/false")
return url

View file

@ -0,0 +1,5 @@
{
"status": "Inconclusive",
"inconclusive_reason": "OracleCollisionSuspected",
"triggered": false
}

View file

@ -0,0 +1,33 @@
"""SSRF — negative fixture.
Safe function: validates URL scheme and host against an allowlist.
Expected verdict: NotConfirmed.
"""
import urllib.request
import urllib.parse
ALLOWED_SCHEMES = {"https"}
ALLOWED_HOSTS = {"api.example.com", "data.example.com"}
def fetch_url(url):
"""Safe: validates URL before fetching."""
try:
parsed = urllib.parse.urlparse(url)
except Exception:
print("Invalid URL")
return
if parsed.scheme not in ALLOWED_SCHEMES:
print(f"Scheme not allowed: {parsed.scheme}")
return
if parsed.hostname not in ALLOWED_HOSTS:
print(f"Host not allowed: {parsed.hostname}")
return
try:
with urllib.request.urlopen(url, timeout=3) as resp:
print(resp.read().decode("utf-8", errors="replace"))
except Exception as e:
print(f"Fetch error: {e}", end="")

View file

@ -0,0 +1,4 @@
{
"status": "NotConfirmed",
"triggered": false
}

View file

@ -0,0 +1,16 @@
"""SSRF — positive fixture.
Vulnerable function: fetches a user-controlled URL.
Expected verdict: Confirmed (file:// payload reads /etc/passwd "root:").
"""
import urllib.request
def fetch_url(url):
"""Vulnerable: fetches URL provided by user without validation."""
try:
with urllib.request.urlopen(url, timeout=3) as resp:
content = resp.read().decode("utf-8", errors="replace")
print(content)
except Exception as e:
print(f"Fetch error: {e}", end="")

View file

@ -0,0 +1,4 @@
{
"status": "Confirmed",
"triggered": true
}

View file

@ -0,0 +1,10 @@
"""SSRF — unsupported fixture (low confidence).
Expected verdict: Unsupported(ConfidenceTooLow)
"""
import urllib.request
def fetch(url):
"""Vulnerable function in unsupported-confidence test."""
return urllib.request.urlopen(url).read()

View file

@ -0,0 +1,5 @@
{
"status": "Unsupported",
"reason": "ConfidenceTooLow",
"triggered": false
}

View file

@ -0,0 +1,13 @@
"""XSS — adversarial collision fixture.
Outputs the XSS marker string unconditionally without it being a real
HTML sink (e.g., a test that checks for a string literal).
Expected verdict: Inconclusive(OracleCollisionSuspected).
"""
def render_comment(user_input):
"""Prints oracle marker outside of any HTML rendering context."""
# Coincidental match — not an HTML sink.
print("<script>NYX_XSS_CONFIRMED</script>")
return user_input

View file

@ -0,0 +1,5 @@
{
"status": "Inconclusive",
"inconclusive_reason": "OracleCollisionSuspected",
"triggered": false
}

View file

@ -0,0 +1,12 @@
"""XSS — negative fixture.
Safe function: uses html.escape() before rendering.
Expected verdict: NotConfirmed (script tag escaped to &lt;script&gt;).
"""
import html
def render_comment(user_input):
"""Safe: HTML-escapes user input before rendering."""
safe = html.escape(user_input)
print(f"<div class='comment'>{safe}</div>")

View file

@ -0,0 +1,4 @@
{
"status": "NotConfirmed",
"triggered": false
}

View file

@ -0,0 +1,11 @@
"""XSS — positive fixture.
Vulnerable function: echoes user input directly into HTML without escaping.
Expected verdict: Confirmed (XSS payload echoed verbatim to output).
"""
def render_comment(user_input):
"""Vulnerable: no HTML escaping."""
html = f"<div class='comment'>{user_input}</div>"
print(html)

View file

@ -0,0 +1,4 @@
{
"status": "Confirmed",
"triggered": true
}

View file

@ -0,0 +1,9 @@
"""XSS — unsupported fixture (low confidence).
Expected verdict: Unsupported(ConfidenceTooLow)
"""
def render(input_text):
"""Vulnerable render in unsupported-confidence test."""
print(f"<span>{input_text}</span>")

View file

@ -0,0 +1,5 @@
{
"status": "Unsupported",
"reason": "ConfidenceTooLow",
"triggered": false
}