Version 0.1.0. Read the first two sections before the reporting section: what salman refuses to do is a larger part of its security posture than what it does.
salman is an engineering and diagnostic tool. It is built for equipment and networks that the person running it owns, or is authorised by the owner to work on.
That is a statement about purpose, and purpose is what the design is built around. salman does not attempt to determine whether you are authorised — it cannot, and a tool that claimed to would be lying. What it does instead is decline to carry the capabilities that are only useful without authorisation, so that the tool is not a convenient starting point for somebody who lacks it.
The following are not implemented, and will not be implemented:
- Network discovery outside user-declared ranges. Discovery is permitted only inside
address ranges a person has explicitly declared as theirs. An address outside them is
refused with
OutsideDeclaredScope. - Credential guessing. Trying candidate credentials against a device.
- Exploitation. salman has no exploit code and takes none as input.
- Fuzzing of live equipment. salman fuzzes its own parsers, on its own inputs, in CI. It does not point a fuzzer at a controller.
- Denial of service. Anything whose purpose or predictable effect is to degrade a device or a network: flooding, malformed-frame storms, resource exhaustion.
- Firmware manipulation. Reading, writing or erasing device firmware.
The refusal lives in crates/salman-core/src/posture.rs. Firmware operations, credential
guessing and denial of service are classified by Effect::is_categorically_refused, and
PostureState::permits returns Denied(CategoricallyRefused) for them at every
posture, including the highest one. There is no configuration option, no environment
variable and no build feature that changes that answer. A test named
firmware_credential_and_dos_effects_are_refused_at_every_posture fails if one is ever
added.
This is a deliberate difference from "off by default". A default can be flipped by a configuration file somebody else wrote. Adding a switch here would not be adding a feature; it would be changing what the tool is.
Everything that can reach outside salman's own process is classified as exactly one
Effect, so the check is total rather than a list of special cases somebody remembered to
write. Three postures govern them, and there is no fourth:
| Posture | May affect |
|---|---|
Observe |
Nothing outside the process. Reads only. The default, and the state everything returns to. |
Simulate |
Devices simulated inside salman. Never a real one. |
Armed |
Real devices, and only with per-call confirmation. |
Two properties are worth stating explicitly because they are what make the model more than a label:
- Arming expires.
PostureState::armtakes a time-to-live and there is no way to arm indefinitely. Once the grant lapses the effective posture isObserveagain, without anybody having to remember to disarm. - Arming cannot be self-granted.
armrequires aUserConfirmation, a type with no public constructor. The only way to obtain one is to put a described request to a human through aConfirmationPromptand have them approve it. An automated caller — including a future agent — must be given a prompt; it cannot be one.
That is no longer hypothetical. salman opens sockets and can write to a device.
salman-modbus-net connects with TcpStream::connect_timeout, listens with
TcpListener::bind for its simulator, and Client::write performs a live write. It is the
first caller of permits, and it went through the posture model because the model was there
before it — which was the point of writing the model first.
What that path does, precisely: Client::write calls
posture.permits(Effect::WriteLiveDevice, ..) and refuses at anything below ARMED, and it
takes a UserConfirmation by value, so one confirmation authorises exactly one write
and cannot be retained for the next. UserConfirmation has no public constructor and can
only be obtained by asking a person, so an automated caller cannot manufacture one. Reads
need no permission: that is what read-only by default means.
Client::write_simulated is deliberately not posture-gated, because nothing on the other
end is real. Nothing in a socket says whether a peer is simulated, so the caller carries that
knowledge: salman_link::Link takes an explicit Peer and refuses to run output mappings
against a live one. No controller mode is changed by any code path here, and no firmware
operation, credential guess or denial of service exists at any posture.
salman has no published security contact address.
That is the honest answer and it is stated rather than papered over. Inventing an address that nobody monitors would be worse than having none: it would convert a reporter's good faith into silence, and they would have no way to tell the difference between a mailbox nobody reads and a report under consideration.
A contact route, and the disclosure timeline that goes with it, will be published here before the first release. Until then, treat this project as having no coordinated disclosure process. If you have found something and want to tell somebody now, use the repository's public issue tracker and use your judgement about what to put in it.
None.
| Version | Supported |
|---|---|
| 0.1.0 | No. Pre-alpha. |
0.1.0 is pre-alpha. There is no support commitment, no patch stream, and no backport policy. Nothing in this repository should be deployed anywhere that a security response would matter, and the absence of a support row is not an oversight to be read around.
When salman gains the ability to capture traffic, those captures will contain process data and may contain credentials: industrial protocols commonly carry authentication material in the clear, and a capture taken to debug a timing problem will pick it up along with everything else. An exported bundle is exactly the artefact that gets attached to a ticket and forwarded on.
The rule, for when that code exists: redaction is on by default in exported bundles, the redaction rules are written down, and they are testable rather than asserted.
Capture code now exists, in salman-capture: it reads classic pcap files and decodes
Ethernet, IP and TCP. It is a reader — salman does not put an interface into promiscuous
mode and has no live-capture path — so the bytes it handles are ones the user already has.
There is still no export-bundle format, so there is nothing yet for the redaction rule above
to apply to; it is written down here so that the default is settled before the first bundle
is exported rather than afterwards.
The dependency graph gets its own gate, because forbidding unsafe in salman's own code —
which the workspace does, with unsafe_code = "forbid" — says nothing at all about the
crates underneath it.
cargo-denyruns in CI, on every push and pull request and on a daily schedule, from.github/workflows/supply-chain.yml. An advisory published today makes a graph that was clean yesterday dirty without a single commit, which is why it cannot only run on push. See https://embarkstudios.github.io/cargo-deny/ and the advisory database at https://rustsec.org/.- The policy is an allowlist.
deny.tomllists the licences salman has considered; one it has not stops the build rather than entering quietly. Unmaintained crates are flagged anywhere in the graph, not just at the top level. Wildcard version requirements are denied. Unknown registries and git dependencies are denied. - Ignored advisories must carry a reason. A bare advisory id records that somebody made
a decision but not why, which makes the decision impossible to revisit. A CI step parses
deny.tomland fails on any ignore entry without both an id and a stated reason. Cargo.lockis committed, and.gitignoresays so explicitly so that nobody helpfully adds it. salman gates on bit-identical output across machines, which is only meaningful with a locked graph.- The toolchain is pinned in
rust-toolchain.tomlto an exact version, with the same reasoning: a byte-for-byte trace comparison across Linux, macOS and Windows means nothing if the machines run different compilers. Bumping the pin is a reviewed change. - Parsers are fuzzed. Six libFuzzer targets in
fuzz/fuzz_targetsrun daily against the Structured Text front end, asserting postconditions rather than only that nothing panicked. Four cover the lexer — valid UTF-8, raw bytes, the strict dialect, and a differential run of both dialects — one covers the parser, and one covers lexing, parsing and semantic analysis together. The declarative test-file reader insalman-testis not fuzzed, and neither is any future protocol decoder. The capability registry records that coverage honestly rather than as a tick.
salman is not assessed against IEC 62443, by anyone, and no assessment is planned. Nothing below is a conformance claim. It is a map of which concepts from that series the architecture actually engages with, written so that a reader can see the size of the gap rather than infer it.
The reference is IEC 62443-3-3:2013 "Industrial communication networks - Network and system security - Part 3-3: System security requirements and security levels" (Ed 1.0),
https://webstore.iec.ch/en/publication/7033.
Concepts salman engages with, as a tool:
- Least functionality. The categorical refusals above remove whole classes of capability from the tool rather than disabling them.
- Use control, applied to the tool itself. Nothing reaches a real device without an explicit, expiring, human-granted posture and a per-call confirmation that names the device, the address, the current value and the value to be written.
- Restricted data flow. Discovery is confined to ranges a person declared. There is no implicit scope.
- Auditability. Every effect is classified, every denial carries a reason fit to show a user, and traces are deterministic and fingerprinted, so a run can be reproduced and compared byte for byte.
- Resource availability, as a discipline on salman's own code. Untrusted input is treated as hostile: parsers are bounded and fuzzed, diagnostics are capped so hostile input cannot exhaust memory, oversized sources are refused rather than loaded, and a scan has an instruction budget so a runaway loop stops the task instead of hanging the process.
Concepts salman does not address at all:
- Identification and authentication of users. salman has no user model, no accounts and no roles.
- Anything cryptographic: confidentiality, integrity or authenticity of communications. salman implements no cryptography.
- Zone and conduit definition, network segmentation, or any control over the network it will eventually observe.
- Security levels, target or achieved, in the IEC 62443 sense. salman has no basis to claim one.
- Patch management, backup and restore, or any operational lifecycle requirement. Those are properties of a deployed system, and salman is not one.
- Compensating countermeasures, risk assessment, and every process requirement in the series. salman is a tool used by people who do that work; it does not do it.
The short version: salman borrows a handful of ideas from IEC 62443 about how a tool that touches control systems ought to behave, and it addresses none of the requirements the series places on a system. Those are different things, and conflating them would be the first step towards a conformance claim nobody could support.