If you updated Docker Desktop recently — version 4.63 or later — a new command is already installed on your machine: docker agent. No setup, no announcement you had to read. The repository describes it as a way to "build, run, and share AI agents with a declarative YAML config," it speaks to OpenAI, Anthropic, Gemini, Bedrock, Mistral, xAI, and Docker's own model runner, it takes "any MCP server (local, remote, or Docker-based)," and it ships preinstalled. Docker says they use it to build itself. That is several million dev machines quietly gaining an agent runtime, one auto-update at a time.
I spent an evening in the repo and the docs instead of writing another framework take, because this project sits exactly on the fault line I keep coming back to: the gap between what an agent can read and what it's allowed to do. Two years ago that gap was my problem — I wrote a seatbelt hook for my own coding agent, attack-tested it, and ended the post at the only wall that held: the operating system. Docker just shipped a productized version of that wall. The engineering is genuinely good. The default is the story.
What actually ships
The model is Claude Code's converted to YAML: agents as config, not code. A toolsets block declares what the agent gets — type: filesystem, type: shell, type: mcp for anything from Docker's MCP catalog ("hundreds of MCP servers," the docs say, attachable with a ref: line). There is a permissions page, a secrets guide, OpenTelemetry tracing. The README reports anonymous telemetry and an Apache-2.0 license, 10,000-plus commits of movement.
Read as a positive policy, the toolset block is the right idea — it is the same inversion my seatbelt's second version went through: stop enumerating what's forbidden, declare what's allowed, let the unlisted fail closed. A runtime where the tool surface is a diffable YAML file is a runtime you can review in a pull request. Credit where due.
The wall, as documented
The sandbox mode docs describe something above my pay grade of hook scripts. Enable it and the agent runs inside a Docker Sandboxes VM — not a raw container, an orchestrator around a dedicated sandbox CLI. The mount table is explicit: the working directory read-write; the agent config and kit directories read-only; everything else invisible — "other host files are not visible to the agent." The VM gets its own $HOME.
The network story is the part I would have asked for and didn't expect: a default-deny egress proxy, with a per-run allowlist covering the model gateway and package registries, extensible only by declared hosts (runtime.network_allowlist) or a persisted docker agent sandbox allow. Default-deny egress is the control that actually contains a prompt-injected agent, because the exfiltration path dies at the proxy regardless of what the model decided. Cloud mode implies the sandbox and refuses to upload host API keys.
This is the wall. It is better designed than anything I bolted together, and I want it on.
The finding
--sandbox defaults to false.
Read the docs' own phrasing: enable it per run (docker agent run --sandbox), bake it into the YAML (runtime: sandbox: true), or attach it to an alias. Every path is opt-in. Out of the box, docker agent run agent.yaml runs on the host — where type: shell and type: filesystem mean what they have always meant on a laptop: your user account is the blast radius. The sandbox with its read-only mounts and its default-deny proxy exists, documented, well-built — in the glovebox.
Defaults are policy. Every mainstream harness that ships a safety mode behind a flag has bet that users read docs, and the track record of that bet is the entire genre of "my agent deleted the database" posts. dev.to already has one for the neighboring toolchain — an agent deleting every container on a machine through MCP tool permissions — and Docker's MCP catalog makes attaching that power a one-click, YAML-declared convenience. Convenience is not a vulnerability. Convenience is what decides whether the vulnerability gets exercised.
Where the doors remain
Even with the sandbox on, three doors don't close, and the docs are honest about at least one of them:
The tool supply chain is context supply chain. Isolation contains what code does; tool poisoning attacks what the model reads. An MCP server inside a perfect VM still returns tool descriptions that carry instructions — the model consumes them as context, and no mount table filters text. The catalog's one-click attach is the new blocklist problem from my seatbelt post, one layer up: the trust decision moved from the shell command to the tool definition, and most reviews of an agent YAML stop at the toolset names.
Secret redaction is best-effort, in Docker's own words — the docs note auto-kit redaction may let obfuscated tokens slip through. Combined with "previous sandboxes are never deleted automatically," that means tokens can persist in stopped sandbox state — the docs remind you removal takes sbx rm --force, and that stopped sandboxes may still incur storage charges. Sensitive work needs the cloud mode's rule — no host keys uploaded — or nothing at all.
Mutable tags break reproducibility, so the docs say to pin remote kits and images by digest. Correct advice, and nobody's default.
And one layer is missing entirely, not just loosely configured: the docs ship OpenTelemetry tracing, which answers what happened — to a collector you control, in a format you can't sign. Nobody's threat model at Docker includes "the operator edits the transcript afterward," which is fair for them and wrong for anyone running agents that touch production. A trace is a debug story. An audit needs a record you can prove was never edited. That layer does not ship here — or anywhere mainstream yet.
What to steal
# The seatbelt stays ON — every path to a sandboxed run, in order of safety:
docker agent run --sandbox agent.yaml # per-run, explicit
# baked in, so it survives copy-paste of the YAML:
# runtime:
# sandbox: true
# attached to the alias your team actually types:
docker agent alias add safe-coder myorg/coder --sandbox
# pin everything the agent will pull:
# pin remote kits/images by digest (docs' own advice)
# and clean up state that never cleans itself:
docker agent sandbox ls
sbx rm --force <name> # stopped sandboxes persist
Plus the two rules the YAML cannot express: never let type: shell and type: filesystem ship together in a default config you didn't review, and read an MCP server's tool definitions like code — because that is what they are now.
Two honest limits
This is a source read, not a test. I did not run docker-agent for this post; every claim about the sandbox comes from the README and docs, which means the docs' accuracy is an assumption I am lending them. The gap between a sandbox's documentation and its behavior is where I found four bypasses in my own hook, so treat my assessment as "the design is right," not "the wall holds."
And the critique has a shelf life. Ten thousand commits and preinstalled distribution means Docker can flip the sandbox default in one release — which would make the headline of this post obsolete in the best way. I would genuinely rather be wrong by Tuesday.
Your turn
Did your Docker Desktop ship with docker agent yet? Did you enable the sandbox before or after reading this — and if you have run it both ways, what did the sandbox block that the host run would have done? The seatbelt thread continues in the comments.
Top comments (47)
sam, this is a brilliant and necessary teardown of the new docker agent architecture. your point that "defaults are policy" is the most critical takeaway. a sandbox that exists but defaults to off is just a false sense of security waiting for a copy-paste error.
as someone building a secure js sandbox for ai code execution (koda), the "fail closed" (default-deny) philosophy is non-negotiable. if an agent can't be trusted, the environment must assume hostility from line one. on constrained hardware like a $150 phone, a runaway agent making unauthorized network calls or consuming resources is catastrophic, making strict isolation even more vital.
your insight that "the tool supply chain is context supply chain" is spot on. even in a perfect vm, if an mcp server's tool description contains a prompt injection, the model will execute it. isolation contains the blast radius, but it doesn't fix the poisoned context.
given that pinning by digest is the only real fix for mutable tags, do you think orchestrators should eventually enforce strict digest pinning at the parser level for any remote kit/mcp reference, rather than just recommending it in the docs?
fantastic write-up. the seatbelt thread continues! 🐯
sam, the "two-writer rule" is a massive insight. you’re absolutely right: parser-level enforcement fixes mutability, but it doesn't fix provenance. separating the proposal (the config) from the approval (a distinct, non-agent process) is the exact separation of duties needed to prevent self-attestation loops.
and i have to say, reading that my "$150 phone is a genuinely good adversarial lab for that boundary" is one of the best compliments i’ve received as a builder. when you have zero margin for error or wasted resources, every single control truly has to earn its place. it forces ruthless architectural discipline.
thank you for taking the time to break this down so clearly. the "warn-and-record vs. hard-fail" grading by blast radius is going straight into my mental model for koda’s sandbox. 🐯🛡️
That discipline — every control earns its place — is the thing most teams only learn after an incident, and you're starting with it, which is the unfair advantage. One habit to keep as KODA grows: break your own sandbox before anyone else does, and write down what fell out — the failures you publish become the spec everyone else trusts. Build well, Harun. 🐯
sam, "break your own sandbox before anyone else does" is going straight into the koda engineering handbook.
you’re absolutely right: publishing the failures is what actually builds the spec and the trust. treating the sandbox like a chaos engineering target from day one, rather than waiting for a post-incident review, is the only way to stay ahead of the curve.
thank you for the mentorship-level advice and for taking the time to engage so deeply on this. it means a lot coming from someone with your experience.
build well! 🐯
Building the handbook entry is the real move — written rules outlive the motivation that wrote them. And the $150 phone stays my favorite adversarial lab for exactly the reason you landed on: zero margin makes every control argue for its place. Come back with what the chaos runs shake loose; that list will be the best part.
sam, exactly! the $150 phone is the ultimate adversarial lab because it strips away all the safety nets of modern dev. if a control, optimization, or feature doesn't earn its place under those constraints, it doesn't belong in the codebase at all.
looking forward to sharing what the chaos runs uncover. the handbook is going to be solid. 🐯🛡️
Looking forward to the share — and one habit worth keeping from day one: publish the chaos-run log, not just the wins. The failures you document become the spec everyone else trusts. 🐏🚀
sam, that is a brilliant takeaway. "publish the chaos-run log" is going straight into my notes.
documenting the failures, edge cases, and weird bugs (like the netlify credit trap or mobile keyboard quirks) builds way more trust than a polished highlight reel. it shows the real engineering process.
thanks for the great advice! keeping that habit from day one. 🐯📝
The "wall that never turned on is invisible to diffs" thread is where the real fix lives, and I'd push it one layer further. CI can lint the YAML for
runtime: sandbox: true, but that's still a declaration, not a fact — the only ground truth is what actually ran on the machine. The primitive missing here is runtime attestation: the sandbox already spins up a fresh VM per run, so the cheapest durable control is an emit-at-start receipt — sandbox on/off, image digest, allowlist version — written somewhere the agent can't edit, keyed to the run ID.That also answers the unsigned-traces gap. Sam is right that OpenTelemetry gives you what happened, not provably what happened — but you don't need attested auditing for the whole agent to get value. A signed "sandbox state at start" record plus the egress proxy's allow/deny log covers the exfiltration question, which is the threat the wall exists for. Everything downstream of a sealed start-state is explainable; everything upstream of it is theater.
For Chris's question on a recommended team default: don't rely on the YAML flag alone — wrap
docker agentin a launcher that injects--sandboxunconditionally and pins the toolset list, then treat any run that bypasses the launcher as an incident. The flag is a seatbelt; the launcher is the interlock.This is the convergence that makes the thread more than a thread: the emit-at-start receipt — sandbox state, image digest, allowlist version, keyed to the run, outside the agent's write path — is the exact primitive NoireBox ships (
run_witnessplus the sealed attestation), and "the flag is a seatbelt; the launcher is the interlock" is the deployment story I was missing. Everything downstream of a sealed start-state is explainable — that's the line the README should carry. Credit where the design lands: an attestation nobody can edit, keyed to the run ID, is what turns the wall from a feature into a fact.That’s an important distinction: passing CI proves what was reviewed, not necessarily what the runtime eventually loads. Binding the reviewed configuration to a hash-identified artifact would close part of that gap, but I’d also want the runtime to verify the identity before execution. Otherwise, a correct artifact can still be replaced or overridden after the check. The stronger invariant is that the exact configuration approved by policy is the one the agent actually runs with.
That's the invariant — and it's the one that just shipped in the receipt layer as
state_binding: the receipt declares the state version it was judged against, and divergence fails loudly. Your phrasing is the general form: the exact configuration approved by policy is the one the agent actually runs with — approved, hashed, verified at execution, with the hash of the reviewed artifact bound into the run's own receipt so the CI-passing config and the booting config can never silently diverge. The hash binding closes half the gap; your runtime-side verification closes the other — both halves needed, neither sufficient alone.That makes the invariant much more concrete. I like that divergence fails loudly instead of leaving the receipt looking valid against a state the agent never actually used. One follow-up question is how that failure is surfaced operationally: can the system distinguish a stale-state rejection from a policy denial or an execution failure? Keeping those outcomes separate would make recovery much easier, because each points to a different fix, refresh the state, review the permissions, or investigate the runtime.
Yes — and that's what the finding statuses are for: the divergence, the denial, and the failure are three different sealed outcomes, not three flavors of "error." Stale state ships as its own finding (
receipt_expired_by_state_changein receipt v0.2.2) — the fix it routes to is refresh-and-re-run: new state version, new gate pass, new receipt. Policy denial is the gate's own sealed decision with the policy version cited — routes to permission review. Execution failure is the runtime's observation — exit codes and the transport attempt, routes to the runtime. The statuses are the recovery routing table: when each sealed outcome names its fix in the note, the operator reads the next action instead of reverse-engineering it. Your three-way split is exactly why the taxonomy is flat and boring on purpose.Good catch - a wall that never turned on is invisible to diffs, so I would treat sandbox enforcement as a runtime assertion: the launcher verifies the effective sandbox state (container config or cgroup namespace) and fails closed if it differs from what the YAML declares. On the chicken-and-egg edge, telemetry from the teams that opted in becomes the proof for the fleet that opted out - which is exactly why the default should flip only once that data shows the permission model held.
That's the runtime-assertion shape, and it's the same invariant that just shipped in the receipt layer:
state_binding— the receipt declares the state version it was judged against, and divergence fails loudly instead of silently. Declared YAML versus effective sandbox is the same gap as declared predicate versus executed bytes: the launcher verifying the cgroup namespace before the run is that receipt's prerequisite move. And your telemetry point solves the chicken-and-egg the honest way: the opt-in fleet isn't the market for the default, it's the evidence pipeline for it — the default flips when the data says the wall held, not when the docs say it should have.Two things on state_binding. First, version granularity: if the state version is a global epoch, divergence detection is either too coarse or too noisy - a receipt judged against v417 stays valid while unrelated fields churn, and any change invalidates everything. Scope the binding to the field set the predicate actually read (dependency-set invalidation, not a global epoch), otherwise you get silent staleness on one side and constant false divergence on the other. Second, the fail-loud path needs its own receipt: when the launcher aborts on divergence, that event gets logged with the declared-versus-effective diff in the same log as successful runs - otherwise "the wall held" is unprovable, you can only show it never fired, not that it fired and blocked.
On flipping the default from evidence: agreed, with one design rule - pre-register the flip criterion (divergence rate below X over N runs, zero silent escapes) before the fleet data lands, or "the data says the wall held" becomes post-hoc storytelling. And the opt-in fleet is biased toward clean workloads, so pair it with shadow mode on a non-opt-in sample: compute the effective sandbox and diff without enforcing, to get an unbiased divergence rate before the default flips.
Both land, and the field-set scoping is the sharper half: a global epoch is the state_binding equivalent of a SWE-bench score — one number that hides which fields the predicate actually depended on. Dependency-set invalidation is the transport attempt at the state layer: the receipt binds to the specific fields the check read, and only those fields' changes trigger the divergence. The fail-loud receipt is the same inversion as everywhere else — a wall that held is a claim unless it's in the journal, and the divergence event beside the successful runs is what makes "the wall held" provable instead of absent. The pre-registered flip criterion and the shadow mode are the controls that separate evidence from story: you can't measure the wall from the fleet that opted in, and you can't claim "the data says the wall held" without pre-registering what "held" means before the data lands.
"A trace is a debug story. An audit needs a record you can prove was never edited" is the line I'd keep from this. I hit the same thing from a different side. The rules my agents run under are a file too, and if the process that loads them can also rewrite them, they drift and nobody notices. So I hash that file when it loads and check it again before every pass. A changed rule shows up as a finding instead of a quiet change in behavior. Your reply to Carlos about the baseline being sealed by whoever reviewed it, not by the connector, is the same idea one layer over.
The line that stuck with me is "Defaults are policy." Shipping a well built sandbox behind --sandbox false means the wall exists for people who already read the docs, and the host run path stays the path of least resistance. That is the same shape as every "my agent deleted the containers" post on this site.
What I like about the design itself is the pairing of an allowlisted toolset with default-deny egress. The YAML review catches what you meant to attach. The proxy catches the shell tool that then curls something you never listed. I would keep both; treating the VM as a substitute for a tool allowlist leaves the host key and catalog attach problems untouched.
Curious whether you have a recommended default for teams that adopt docker agent: force sandbox true in the shared YAML template, or refuse to run unless the alias already pins it.
Both, honestly — they fail differently. The shared template is policy (it catches the team that forgets); the alias pin is the interlock (it catches the run that bypasses the template — a local override, a copied command, a CI job built from the wrong file). @samii's launcher framing below is the version I'd ship: the template sets the default, the wrapper enforces it unconditionally, and any run that bypasses the launcher is an incident rather than config drift. And your point about the VM not substituting for the allowlist is the one I'd underline — the proxy catches egress, the allowlist catches attachment, the sandbox catches blast radius. Three walls, three different leaks; the team that keeps all three is the one that survives the copy-pasted run.
Three walls, three different leaks is a better summary than mine. I also like treating a run that bypasses the launcher as an incident instead of drift, because that turns a config question into something someone has to explain.
One follow up on the interlock: make the wrapper write a small record for every run it starts, with the template hash and sandbox flag. Then the incident check is a diff between runs the platform saw and runs the launcher recorded, and the copy pasted run shows up on its own instead of waiting for someone to notice.
How would you detect a bypass in practice today: the launcher logs runs and you reconcile, or the runtime refuses anything without a launcher token?
Both, but sequenced — and your record makes the first one shippable this week. Reconcile first: the launcher writes the per-run record before the run starts, not on success — template hash plus sandbox flag — because the incident you can't see is the run nobody logged. Then the check is the diff between two streams the platform already has: runs the runtime saw, runs the launcher recorded. A copy-pasted run exists in one stream and not the other, and the missing pairing is the incident — mechanical, no one has to notice anything. The template hash is the load-bearing half: it commits the record to what was actually launched, so a legit-looking record can't be reused as cover for a different run.
The refusal is the endgame, but it carries a condition: it's only as strong as the token's arming path. If the launcher token lives in an env var or a config file the agent can read, the wall degrades silently — the runtime keeps refusing, the agent keeps passing a minted token, and the dashboard shows the wall holding. That's the canary lesson: a gate armed through the same control plane the agent can reach isn't grade-1, whatever it calls itself. So the runtime-side refusal wants the token checked by a process on the far side of a boundary the agent doesn't write — a uid-gated socket that fail-closes where the platform can't identify the peer is the cheapest version I know that survives an audit.
The sequencing is the honest part: reconcile detects on stock platforms today and turns a bypass into an incident someone explains; the runtime refusal prevents once the token is out of the agent's reach. Ship the first, gate the second on that condition — a refusal without it is the same three walls one more time, just quieter.
Reconcile first, then gate refusal on a token the agent cannot mint, is the sequencing I was missing. Treating a missing pairing as the incident is what makes that first step shippable without new infrastructure.
One follow up on the arming path: if the launcher token still lives where the agent can read it, what is the cheapest move that puts the check on the far side of that boundary without rebuilding the runtime?
Are you running a uid-gated socket pattern in production yet, or is that still the design you would reach for once reconcile is in place?
Running code as of this week, not a design sketch: the receipt tool's keyholder is exactly that pattern — a daemon loads the operator key once into memory, and every signature request is a unix-socket round trip;
--allow-uidgates the connecting peer by uid (the kernel answersSO_PEERCREDout-of-band on Linux), and it fails closed where the platform cannot identify the peer — on macOS the refusal is itself the verifiable answer, readable from the client asunavailable(<daemon platform>). After start, the PEM file leaves the agent-reachable volume entirely: the material exists in one process and nowhere else.The cheapest move for your launcher, without touching the runtime, is the same shape in three pieces: a sidecar process that loads the launch credential once; a socket file created
0600on a path outside the agent's mounts — reachability replaces readability; and a peer-credential check answered by the kernel, not by anything the client sends. Your launcher's signer call becomes a round trip on that socket —.sign(bytes)and nothing else crosses. A token in an env var is a fact the agent can copy; a socket path is a capability — copying it grants nothing, because the authority question ("which process connected") is answered about the connection, not asserted inside it.The honest boundary is deployment, not crypto: the sidecar runs as a different uid than the agent, and the socket path must live where the agent cannot create or replace it — that premise is priced in the docs rather than hidden. Sequence it the way we said: reconcile first, the sidecar bolted on when a bypass has to become impossible instead of visible — it's an afternoon, not a rebuild.
"Defaults are policy" matches what I saw on our side. Our agent runs per ticket inside a stack that has no other mode: its own branch in every repo, its own services, a copy of the dev database. There is no host run to forget a flag on, and the steps that leave the box (merge, deploy, rollback) are scripts, not the agent. Your tool-description point is the one that still gets through that. Do you review MCP tool definitions in the same PR as the YAML?
Yes — and that's the pattern that shipped in v0.11 (
toolset-pin): capture the MCP server'slistToolsresponse at registration, hash it, seal the hash as the reviewed baseline — then the diff runs at every session start, and a changed description is a finding rather than a surprise. The PR review is exactly where the baseline earns its seal: the reviewer sees the tool surface and the pin together, and an upstream MCP update that shifts the surface fails the diff instead of the agent. Your stack — per-ticket branch, scripts for every leave-the-box step — is the architecture that makes the flag unnecessary; the rest of us need the pin because the run path is where our defaults go to die.Sealing the listTools hash at registration turns an upstream MCP update into a review event instead of a runtime surprise. When the diff fails at session start, what happens to the run: does the session refuse to start, or drop the changed tool and carry on with the rest of the toolset?
Both, tiered by what the tool can touch — the same grading the thread gave digest pinning. A changed description on a read-only tool is a finding printed at session start (
toolset_drift: changed tool dropped, baseline version cited, diff sealed) and the session carries on with the witnessed remainder — the sealed baseline means the dropped tool is a record, not a silence. A changed tool in the egress-relevant set — anything that can reach the network, the filesystem, or credentials — fails the session: that's the SCAR-45 lesson at the toolset layer, a surface that changed under a receipt that vouches for the old one is exactly the drift the pin exists to catch. And the third case your question implies — the MCP server rolls a breaking change that moves a tool's whole semantics — is the baseline-update PR: the drift finding IS the review event, and the new hash gets sealed after the same human eyes that reviewed the YAML. Refuse-to-start stays reserved for the run that can't witness its toolset at all — no sealed baseline is the "wall that never turned on" of the toolset layer.The point that matters most here is the gap between what an agent can read and what it is allowed to do. A sandbox that exists but ships switched off protects nobody, because most teams never revisit defaults once something works.
When we put agents into a client environment, the egress policy and tool permissions are written into the definition of done before any prompt work starts. If the agent can only reach the three hosts it needs, a bad tool call becomes a failed request instead of an incident. It also makes the review conversation with a security team much shorter.
I would be curious whether the YAML config lets you pin the sandbox setting per agent so it cannot be overridden locally. That would make it much easier to enforce across a team.
"Defaults are policy" is the whole post for me.
Someone made the same point on my coding agents post yesterday: a sandboxed, read-only agent can still leak things through one "helpful" curl. Default-deny egress kills that path no matter what the model decided, so it really should be the default.
The MCP part worries me more though. Tool descriptions are text the model reads as instructions, and most reviews of the YAML stop at the toolset name. Did you see anything in the docs about pinning or diffing the tool definitions themselves, not just the image digest?
Straight answer: no — the docs pin the image, and the tool definitions ride outside the pin. A pinned image pins the server code, which constrains what it can serve, but tool definitions are produced at runtime: config-driven, env-dependent, and in the remote case not pinned at all — an SSE/HTTP MCP reference is a URL, and whatever it serves this morning is what your model reads. So the layer you're pointing at is the real one: the toolset is the lockfile nobody writes.
The shape I'd trust: capture the listTools response at registration, hash it, seal the hash as the reviewed baseline — then on every session start, diff the served definitions against it and surface a changed description as a finding, not silently. One caveat carried over from the image-pin argument: the baseline has to be sealed by whoever reviewed it, not by the process that connects — a baseline the connector can rewrite drifts with the thing it measures. Your curl example is the same philosophy one layer up, by the way: default-deny egress pins the network path, toolset diffing pins the context path — the two halves of the supply chain the model actually travels.
The default-off issue highlights a broader deployment problem: a security control can be well designed and still provide no protection if the deployed configuration never activates it. At IT Path Solutions, we see the value in treating critical agent safeguards as deployment requirements rather than optional runtime preferences. A CI check could reject configurations that enable shell or filesystem access without the required sandbox, network restrictions, and reviewed tool definitions. That shifts the burden from remembering a security flag to proving that the deployed agent meets a minimum security baseline. The important distinction is between having a safeguard available and making sure every production path actually uses it.
The CI-as-bouncer framing is right — the check doesn't have to be clever, it has to be mandatory. One gap worth designing for: CI proves the config at review time, drift happens at run time. The YAML that shipped and the runtime that boots should be the same artifact, hashed — otherwise the baseline is a document, not a control.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.