Skip to content

feat(baremetal): the backup space nobody could see, and who was allowed on it - #258

Draft
Denis-hamon wants to merge 24 commits into
ovh:feat/ip-administrationfrom
Denis-hamon:feat/baremetal-backup
Draft

feat(baremetal): the backup space nobody could see, and who was allowed on it#258
Denis-hamon wants to merge 24 commits into
ovh:feat/ip-administrationfrom
Denis-hamon:feat/baremetal-backup

Conversation

@Denis-hamon

Copy link
Copy Markdown
Contributor

What this adds

A dedicated server comes with a Backup FTP space. The CLI could neither show it nor say who was
allowed to reach it — nine routes, none of them wired.

ovhcloud baremetal backup orderable <server>
ovhcloud baremetal backup ftp show|create|delete|password <server> [--wait]
ovhcloud baremetal backup ftp authorizable-blocks <server>
ovhcloud baremetal backup ftp acl list|get|add|set|delete <server> [<ip_block>] [--ftp|--nfs|--cifs]
ovhcloud baremetal backup cloud show|offer|create|delete|password <server> [--reveal]

9 routes, 15 commands. Stacked on #257; only the last commit belongs to this pull request.

What the account said

All thirty-five servers, every backup route:

route answer
features/backupFTP 7 servers have a space, 28 answer 404
features/backupFTP/access 1 to 2 rules on each of the 7
features/backupFTP/authorizableBlocks 115 blocks on the European servers, 5 on the Canadian ones
features/backupCloud 0 of 35
backupCloudOfferDetails 403 on 34 of 35, sizes on the thirty-fifth
orderable/backupStorage 200 on all 35 — 31 orderable, 4 not

The --wait this commit shipped first was wrong, and only running it showed that

A real creation on an authorised server: the space answered 200 after seven minutes with an empty
ftpBackupName
, while its createBackupFTP task was still doing. Every write against it was
then refused:

409  "You have a backupFTP create task pending, you cannot release your backupFtp until it's done."

So waiting for the object to exist handed back a space nothing could be done with, eight minutes
early
. It waits for the name now — the field that says the space has been placed on a storage
server.

step measured
POST features/backupFTP → object answers 200 7 min 12 s (ftpBackupName empty)
ftpBackupName filled 15 min 57 s
task done 16 min 27 s
backup ftp delete --wait 1 min 09 s
final state read outside the CLI 404 — back where it started

The name and the task completion fell in the same 30-second sample, so the fixed wait is right to
within that interval; the object-exists check was wrong by nearly nine minutes.

The two directions of that wait are not the same question either. A space halfway through its
creation is not ready, but it is very much still there — a deletion that reused the readiness
predicate would have reported "gone" about a space being provisioned. They are separate now, and a
sabotage that merges them turns the test red.

Three more things the account had to be asked

An access rule can open nothing. The API takes three booleans and accepts all three false: a
rule that appears in the access list, says an IP block is allowed, and lets it reach nothing. That
is worse than no rule, because it reads as protection that is in place. It is refused.

The blocks a space accepts are per server and regional — 115 on the European servers, 5 on the
Canadian ones — so acl add checks the block against the server rather than sending it to be
refused. The refusal counts them and names the command that lists them: printing 115 lines in an
error message answers worse.

The cloud backup password reset answers four credentials in the response body, unlike the
Backup FTP one which the API mails. They are withheld behind --reveal, and the substitution
happens on the object so -o json is covered by the same decision as the table — a password reset
is exactly the command run inside a pipeline whose output is kept.

Verified against the API

Reads, on the servers that have a space: backup ftp show (quota, storage server, type),
acl list (the rules with their protocols spelled out rather than three booleans to cross-read),
authorizable-blocks, backup orderable on both an eligible and an ineligible server, and
backup cloud offer on the one server it covers and on one of the thirty-four it does not — where
the 403 is reported as an answer, not as a failure.

Writes, run end to end on an authorised server and then undone: create, the auto-created ACL the
provisioning leaves behind, acl set, acl delete, the FTP password change, and the deletion. The
server is back to answering 404, read outside the CLI.

Five refusals executed against the API: no space, no space so no access list, a block the server
does not accept, a rule opening no protocol, and no cloud backup.

Notes

  • IsNotFound moves to internal/services/common: it was written twice already in this stack, and
    a second copy of a routine that decides between "this does not exist" and "the API failed to
    answer" does not expire — it stays wrong in silence while the original is fixed.
  • backup ftp delete takes the strongest guard the CLI has, in the API's own words: "Terminate
    your Backup FTP service, ALL DATA WILL BE PERMANENTLY DELETED"
    . backup cloud delete takes the
    weaker one, also on the API's own statement: "This does not delete container data."

Denis-hamon and others added 3 commits August 18, 2026 17:02
…to change

`vps service-info edit myvps --renew-period 12` used to send this:

    {"renew":{"automatic":false,"deleteAtExpiration":false,"forced":false,
              "manualPayment":false,"period":12}}

The renewal settings are booleans bound to a shared struct carrying no
`omitempty`, so every one of them was marshalled at its zero value and won the
merge against the fetched resource. Changing the renewal period therefore also
switched automatic renewal off — on a service that had been renewing itself for
years, without a word in the output saying so.

Webhosting already built its payload from `cmd.Flags().Changed`, and did not
have the defect. This promotes that builder into `common`, so `vps` gets it too
and the next `service-info edit` cannot reintroduce the bug by reaching for the
struct.

Reading `Changed` rather than the values keeps `--renew-automatic=false`
working: pflag records a flag as changed whatever value it was given, so an
explicit false is still sent while an absent flag stays absent. Both cases are
covered by a test, and each test was checked against the failure it exists to
catch.

The shared mutable `ServiceInfoSpec` goes away with the last thing that read
it, and the five flag registrations repeated across four commands become one
call, which also settles the two spellings of the period's help text.

Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
…ayer

Declaring cobra flags is the command layer's job, and internal/services/common
was the only service package doing it — the shared flag helpers all live in
internal/cmd. Raised in review of this PR.

The registration moves; the table does not. Both halves need the flag name —
one to declare it, the other to read whether the operator set it — and that
name is the only thing tying them together. Splitting it into two copies would
mean a rename could touch one side and leave the other silently no longer
sending a setting, which is the exact failure this PR exists to fix. So
common.ServiceInfoRenewFlags becomes the exported description, internal/cmd
registers from it, and the payload builder keeps reading it.

Checked by renaming an entry in that table and watching the service-info tests
fall: the two halves still move together.

Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ed on it

A dedicated server comes with a Backup FTP space, and the CLI could neither
show it nor say who was allowed to reach it. Nine routes, none of them wired:
the space itself, the access list that guards it, the two passwords, the cloud
backup beside it and the capacities that can be ordered.

Measured on a real account, on all thirty-five servers: seven have a Backup FTP
space, none has a cloud backup, thirty-one can order extra storage and four
cannot. So the reads are demonstrable and the writes had to be run.

Running them found a defect in this commit's own --wait. The space answers 200
after seven minutes with an empty ftpBackupName, while its creation task is
still going — and the API then refuses every write against it: "You have a
backupFTP create task pending, you cannot release your backupFtp until it's
done." Waiting for the object to exist therefore handed back a space nothing
could be done with, eight minutes early. It waits for the name now, which is
what says the space has been placed on a storage server. Measured end to end:
sixteen minutes and twenty-seven seconds to create, one minute nine to delete.

The two directions of that wait are not the same question either. A space
halfway through its creation is not ready, but it is very much still there, so
a deletion that reused the readiness predicate would have reported "gone" about
a space being provisioned.

Three more things the account had to be asked. An access rule takes three
booleans and the API accepts all three false: a rule that appears in the list,
says an IP block is allowed, and lets it reach nothing — worse than no rule,
because it reads as protection. The blocks a space accepts are per server and
regional: 115 on the European servers, 5 on the Canadian ones, so the rule is
checked against the server rather than sent to be refused. And the cloud backup
password reset answers four credentials in the response body, unlike the Backup
FTP one which the API mails — they are withheld behind --reveal, on the object
so that -o json is covered by the same decision as the table.

A server the cloud backup offer does not cover answers HTTP 403 — thirty-four
of the thirty-five. That is a business answer, not a permission problem, and it
is reported as one.

Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Denis-hamon and others added 5 commits August 20, 2026 16:14
A hostname, an address, an order id and a colleague's name read off a live
account had been used as fixtures and as examples in comments. This repository
is public: what goes in stays in. The values are replaced with synthetic ones
of the same shape — RFC 5737 documentation addresses, hostnames built on them,
identifiers of the same length — so the tests keep exercising the same parsing.

This is the fourth time in this series that live account data reached a commit,
after a live IP migration token, a test server's address, and a real invoice
id. The first three were fixed one at a time as they were noticed; this is the
sweep that should have followed the first one.

Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
A hostname, an address, an order id and a colleague's name read off a live
account had been used as fixtures and as examples in comments. This repository
is public: what goes in stays in. The values are replaced with synthetic ones
of the same shape — RFC 5737 documentation addresses, hostnames built on them,
identifiers of the same length — so the tests keep exercising the same parsing.

This is the fourth time in this series that live account data reached a commit,
after a live IP migration token, a test server's address, and a real invoice
id. The first three were fixed one at a time as they were noticed; this is the
sweep that should have followed the first one.

Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
@Denis-hamon
Denis-hamon changed the base branch from main to feat/ip-administration August 20, 2026 15:19
Denis-hamon and others added 12 commits August 20, 2026 22:45
Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
… elsewhere

`backup cloud password` withholds the four credentials it resets, on the
reasoning that a password reset is exactly the command somebody runs with the
output on screen or in a build log. The same four are in the object, one level
down: archive and storage each carry an sftp and a swift block, and each of
those a password — format: password in the schema, all four of them. `show` and
`create` printed that object whole.

Masking on one command and printing on the two others is not a policy, it is an
oversight, and `show` is the likelier leak of the three: it is a read, so it can
be run again and again, including by a script that logs what it reads.

Both now go through the same withholding, and both gained --reveal for the
operator who actually needs the credential. The walk keys on the field name
rather than on the four known paths: this object is a tree the API grows, and a
third container — or a third protocol under an existing one — would otherwise be
printed in the clear by a masker that still looked like it was working. It
copies rather than edits in place, so its correctness does not depend on the
order in which it is called.

Three tests. Two sabotages, both compiling: one that returns the object
untouched, one that masks only the top level — where none of the four are.

Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
Regenerated by make doc. The flag added to `backup cloud show` and
`backup cloud create` in the previous commit changes their pages, and the CI
gate compares the tree against what docgen produces.

Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`backup cloud offer` read every HTTP 403 as "no cloud backup offer covers this
server". Thirty-four of the thirty-five servers measured do answer 403 for
exactly that reason, so the shape was right — but an API key that simply lacks
the grant on this route answers 403 too, and it was told a fact about the
catalogue instead of about itself. Under -o json it was handed offered:false,
which a script has no way to doubt.

The business answer is now matched on what it says. Those 34 servers returned
{"message":"Not available for this server"}; anything else with a 403 is reported
as a refusal, naming the grant to check.

The existing test carried an invented body — "not offered" — which is precisely
how a status-only check passed while the command could not tell the two apart.
It now uses the message the API really returned, and a second test covers the
refusal.

One compiling sabotage, back to matching on the status: red on the new test,
green on the old one, which is the asymmetry worth having.

Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
Promoting `isNotFound` to `common.IsNotFound` updated two of its three call
sites. The third, in the anti-spam statistics path, kept calling the function
that had just been deleted, so the `ip` package stopped compiling — and with it
the whole binary and every branch stacked above.

Two tests already pin both sides of that exact line: a 500 must surface as an
error, a 404 must still read as "not flagged". Neither could fail, because a
package that does not compile runs no tests at all. A build failure is the
finding, never the noise to look past.

Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The wait for a deletion decided on `err == nil`. Every failure to read the space
— a 500, a rate limit, a dropped connection — therefore counted as "not there",
and "not there" is precisely the answer the deletion is waiting for. One bad poll
ended the wait and printed "✅ deleted" over a space that was still there, which
is the worst shape this class of bug takes: the operator is told the thing is
gone.

There are three outcomes, not two: the space answered, the API says it is not
there, and the read did not happen. Only the second is an answer about the space.
A sentinel error carries that distinction — a sentinel rather than a boolean
beside the error, so a caller cannot forget to look at it — and the loop now logs
an unreadable poll and keeps waiting.

The same distinction holds the other way: a 404 is what the route answers for
most of the sixteen minutes a creation takes, so it must not end that wait
either. Both directions are tested.

Five tests, three sabotages, three reds — the first of the three first broke the
build, which proves nothing about a test's teeth, and was rewritten to
reintroduce the original defect while still compiling.

Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
ovh#243 moved the Cobra registration of the renewal flags out of
internal/services/common and into the command layer, and exported the
descriptor table so both halves keep reading one list. That commit landed after
this branch had already taken its parent, so this branch still carried the
version where a service package declares flags — and being the more recent side
of the merge, it would have won and quietly undone the refactor.

Merged rather than rebased: nothing is rewritten, so the review threads on this
PR stay attached to their lines.

Signed-off-by: Denis Hamon <denis.hamon@ovhcloud.com>
@Denis-hamon

Copy link
Copy Markdown
Contributor Author

Human review — Doubt

H1 · --wait waits until the space is USABLE
Dimension: Product fit

Expected: The command only hands back control once the space is writable. A “ready” eight minutes early is a trap: check that the final message does not lie.

🔍 Dry run: nothing was sent. This would have been called:
POST /v1/dedicated/server/ns31633811.ip-162-19-234.eu/features/backupFTP
exit 0

— Denis (Product Manager), through CLI sandbox review page

@Denis-hamon

Copy link
Copy Markdown
Contributor Author

Human review — Does not work

H2 · An ACL that opens no protocol is refused
Dimension: Guard rail

Expected: The CLI refuses. Worse than no rule, it reads as a protection in place.

🛑 an access rule has to open at least one protocol; give --ftp, --nfs or --cifs
exit 1

— Denis (Product Manager), through CLI sandbox review page

@Denis-hamon

Copy link
Copy Markdown
Contributor Author

Human review — Does not work

H3 · Four passwords inside one object
Dimension: Secrets

Expected: show and create mask them. Masking walks by FIELD NAME, so a third container added by the API would be masked as well.

🛑 ns31633811.ip-162-19-234.eu has no cloud backup: OVHcloud API error (status code 404): "The requested object (backupCloud) does not exist" (X-OVH-Query-Id: EU.ext-1.6a8b4514.1195793.4a626d6fe3a0a86dedde4c1134f83d7e)
Create one with: ovhcloud baremetal backup cloud create ns31633811.ip-162-19-234.eu
exit 1

— Denis (Product Manager), through CLI sandbox review page

@Denis-hamon

Copy link
Copy Markdown
Contributor Author

Follow-up on H1, H2 and H3. Two of the three are false negatives, and the third is an absence worth recording.

H2 — the refusal is the pass. an access rule has to open at least one protocol; give --ftp, --nfs or --cifs is the behaviour this check asks for, verbatim: the API accepts all three booleans false and produces a rule that shows up in the list, claims a block is authorised and grants access to nothing. Exit 1 and the 🛑 are correct here. The sandbox never said that a check can be validated by a refusal, and three verdicts on 23/08 turned on exactly this misreading. Eight checks are in that case; they now carry the warning before the click.

H3 — no server on this account has a Cloud Backup. Measured on all 30 reachable servers on 24/08: zero. The 404 was not the command misbehaving, it was the sandbox firing at an object that exists nowhere. The masking of the four passwords therefore cannot be exercised live at all; the check is now announced as replay-only, with that reason spelled out.

H1 — not observable from the button either. --wait is what happens after the write, and the guard only ever lets a dry run through, so the button can show the POST and nothing more. Judged on the recording; the sandbox now says so up front rather than leaving a reviewer to conclude the wait is broken.

A --dry-run already carries the parameters in its message, and a
log.Println sitting just above the branch repeated the same JSON behind
a Go timestamp no other command in this CLI emits:

    🔍 Dry run: nothing was sent. This would have been posted to …
    { "operatingSystem": "debian12_64" }
    2026/08/23 23:47:22 Final parameters:
    { "operatingSystem": "debian12_64" }

The log line moves below the branch. A real run still logs what it is
about to send, which is what it was for; a dry run logs nothing, because
it sends nothing.

The line goes to stderr, so no assertion on stdout could ever have seen
it — which is why it survived every green run. Two tests now redirect the
logger: one that a dry run does not log, and its positive control that a
real run still does, so deleting the line outright would not pass.

Signed-off-by: Denis <denis.hamon@ovhcloud.com>
Mechanical, not a change of behaviour. Placing them just after
TestBaremetalReinstallDryRun put them in the one region of
baremetal_test.go that 23 downstream branches also append to, and in the
import block they all touch as well. Merging the parent into those
branches failed 23 times out of 23, every one of them on adjacency
rather than on a disagreement.

baremetal_test.go goes back to what it was before the previous commit,
so this branch now leaves that file untouched. A new file can only clash
with a file of the same name, and nothing else carries this one.

Signed-off-by: Denis <denis.hamon@ovhcloud.com>
@Denis-hamon

Copy link
Copy Markdown
Contributor Author

Human review — Does not work

H3 · Four passwords inside one object
Dimension: Secrets

Expected: show and create mask them. Masking walks by FIELD NAME, so a third container added by the API would be masked as well.

🛑 ns31633811.ip-162-19-234.eu has no cloud backup: OVHcloud API error (status code 404): "The requested object (backupCloud) does not exist" (X-OVH-Query-Id: EU.ext-1.6a8b4514.1195793.4a626d6fe3a0a86dedde4c1134f83d7e)
Create one with: ovhcloud baremetal backup cloud create ns31633811.ip-162-19-234.eu
exit 1

— Denis (Product Manager), through CLI sandbox review page

@Denis-hamon

Copy link
Copy Markdown
Contributor Author

Human review — Does not work

H1 · --wait waits until the space is USABLE
Dimension: Product fit

Expected: The command only hands back control once the space is writable. A “ready” eight minutes early is a trap: check that the final message does not lie.

🔍 Dry run: nothing was sent. This would have been called:
POST /v1/dedicated/server/ns31633811.ip-162-19-234.eu/features/backupFTP
exit 0

— Denis (Product Manager), through CLI sandbox review page

@Denis-hamon

Copy link
Copy Markdown
Contributor Author

Human review — Does not work

H3 · Four passwords inside one object
Dimension: Secrets

Expected: show and create mask them. Masking walks by FIELD NAME, so a third container added by the API would be masked as well.

🛑 ns0000001.ip-203-0-113.eu has no cloud backup: OVHcloud API error (status code 404): Client::NotFound: "This service does not exist" (X-OVH-Query-Id: EU.ext-4.6a8c73b0.523112.3787859a28588c2145a7641cb7714202)
Create one with: ovhcloud baremetal backup cloud create ns0000001.ip-203-0-113.eu
exit 1

— Denis (Product Manager), through CLI sandbox review page

@Denis-hamon

Copy link
Copy Markdown
Contributor Author

Human review — Does not work

H3 · Four passwords inside one object
Dimension: Secrets

Expected: show and create mask them. Masking walks by FIELD NAME, so a third container added by the API would be masked as well.

🛑 ns0000001.ip-203-0-113.eu has no cloud backup: OVHcloud API error (status code 404): Client::NotFound: "This service does not exist" (X-OVH-Query-Id: EU.ext-3.6a8c8172.1295079.161d2e072f609a195626b187e8c8aa9a)
Create one with: ovhcloud baremetal backup cloud create ns0000001.ip-203-0-113.eu
exit 1

— Denis (Product Manager), through CLI sandbox review page

@Denis-hamon

Copy link
Copy Markdown
Contributor Author

Human review — Does not work

H1 · --wait waits until the space is USABLE
Dimension: Product fit

Expected: The command only hands back control once the space is writable. A “ready” eight minutes early is a trap: check that the final message does not lie.

🔍 Dry run: nothing was sent. This would have been called:
POST /v1/dedicated/server/ns31633811.ip-162-19-234.eu/features/backupFTP
exit 0

— Denis (Product Manager), through CLI sandbox review page

@Denis-hamon

Copy link
Copy Markdown
Contributor Author

Human review — Does not work

H3 · Four passwords inside one object
Dimension: Secrets

Expected: show and create mask them. Masking walks by FIELD NAME, so a third container added by the API would be masked as well.

🛑 ns0000001.ip-203-0-113.eu has no cloud backup: OVHcloud API error (status code 404): Client::NotFound: "This service does not exist" (X-OVH-Query-Id: EU.ext-3.6a8c8172.1295079.161d2e072f609a195626b187e8c8aa9a)
Create one with: ovhcloud baremetal backup cloud create ns0000001.ip-203-0-113.eu
exit 1

— Denis (Product Manager), through CLI sandbox review page

1 similar comment
@Denis-hamon

Copy link
Copy Markdown
Contributor Author

Human review — Does not work

H3 · Four passwords inside one object
Dimension: Secrets

Expected: show and create mask them. Masking walks by FIELD NAME, so a third container added by the API would be masked as well.

🛑 ns0000001.ip-203-0-113.eu has no cloud backup: OVHcloud API error (status code 404): Client::NotFound: "This service does not exist" (X-OVH-Query-Id: EU.ext-3.6a8c8172.1295079.161d2e072f609a195626b187e8c8aa9a)
Create one with: ovhcloud baremetal backup cloud create ns0000001.ip-203-0-113.eu
exit 1

— Denis (Product Manager), through CLI sandbox review page

@Denis-hamon

Copy link
Copy Markdown
Contributor Author

Human review — Does not work

H2 · An ACL that opens no protocol is refused
Dimension: Guard rail

Expected: The CLI refuses. Worse than no rule, it reads as a protection in place.

🛑 an access rule has to open at least one protocol; give --ftp, --nfs or --cifs

— Denis (Product Manager), through CLI sandbox review page

@Denis-hamon

Copy link
Copy Markdown
Contributor Author

Human review — Does not work

H3 · Four passwords inside one object
Dimension: Secrets

Expected: show and create mask them. Masking walks by FIELD NAME, so a third container added by the API would be masked as well.

🛑 ns0000001.ip-203-0-113.eu has no cloud backup: OVHcloud API error (status code 404): Client::NotFound: "This service does not exist" (X-OVH-Query-Id: EU.ext-3.6a8c8172.1295079.161d2e072f609a195626b187e8c8aa9a)
Create one with: ovhcloud baremetal backup cloud create ns0000001.ip-203-0-113.eu
exit 1

— Denis (Product Manager), through CLI sandbox review page

@Denis-hamon

Copy link
Copy Markdown
Contributor Author

Human review — Does not work

H2 · An ACL that opens no protocol is refused
Dimension: Guard rail

Expected: The CLI refuses. Worse than no rule, it reads as a protection in place.

🛑 an access rule has to open at least one protocol; give --ftp, --nfs or --cifs

— Denis (Product Manager), through CLI sandbox review page

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant